跳到主要内容

属性描述符与响应式

属性描述符包含哪些字段?​

  • 数据描述符: 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