属性描述符与响应式
属性描述符包含哪些字段?
-
数据描述符:
value、writable、enumerable、configurable; -
访问器描述符:
get、set、enumerable、configurable; -
字段含义:
configurable: 是否可被删除或重新定义为访问器属性;enumerable: 是否可被for...in、Object.keys枚举;writable: 属性值能否被改写;value: 属性值, 默认undefined;get/set: 读取与写入时调用的函数, 只定义get表示只读, 只定义set表示只写;
-
默认值: 通过
Object.defineProperty定义的属性, 未给出的描述符字段默认为false或undefined;
如何把对象变成不可修改的?
| 方法 | 效果 |
|---|---|
Object.preventExtensions | 禁止添加新属性, 已有属性仍可改可删 |
Object.seal | 禁止增删, 已有属性值仍可修改 |
Object.freeze | 禁止增删改, 已有属性全部变为不可写 |
- 属性级替代: 用
defineProperty给出writable: false, 或用get/set自定义读写行为;
数据劫持如何实现?
Object.defineProperty方案: 遍历对象属性, 在get/set中插入订阅逻辑, 读取与写入时触发对应回调;
const obj = { name: "刘逍", age: 20 };
const p = {};
for (const key in obj) {
Object.defineProperty(p, key, {
get() {
console.log(`${key} 被读取`);
return obj[key];
},
set(val) {
console.log(`${key} 被修改`);
obj[key] = val;
},
});
}
- 局限: 需要预先遍历, 无法拦截新增属性与删除操作;
Proxy+Reflect方案: 代理整个对象, 在 trap 中记录读取、写入、删除, 详见 Proxy 与 Reflect;
const p = new Proxy(obj, {
get: (target, key) => (console.log(`${key} 被读取`), Reflect.get(target, key)),
set: (target, key, value) => (console.log(`${key} 被修改`), Reflect.set(target, key, value)),
deleteProperty: (target, key) => (
console.log(`${key} 被删除`),
Reflect.deleteProperty(target, key)
),
});
如何让对象在判等时逐次返回不同值?
- 机制: 重写
toString/valueOf或定义Symbol.toPrimitive, 使每次隐式转换得到新值, 详见 隐式转换与判等;
const a = { i: 1, toString: () => a.i++ };
a == 1 && a == 2 && a == 3; // true
const b = [1, 2, 3];
b.toString = b.shift;
b == 1 && b == 2 && b == 3; // true