JavaScript 系列 - defineProperty 和 defineProperties

157 阅读4分钟

Object.defineProperty()

使用

Object.defineProperty()  静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

Object.defineProperty(obj, prop, descriptor)

descriptor

  • 数据描述符
    • 一个具有值的属性
    • value 默认为 undefined
    • writable 默认为 false,value是否可以赋值
  • 存取描述符
    • getter 函数和 setter 函数所描述的属性
    • get 默认为 undefined
    • set 默认为 undefined
  • configurable 属性的描述符是否可以修改
    • 默认为 false
    • value 是否可以删除
  • enumerable 属性是否可以枚举
    • 默认为 false
    • for...in 循环和 Object.keys()
    • 展开运算符
// 重复利用同一对象
function withValue(value) {
  const d =
    withValue.d ||
    (withValue.d = {
      enumerable: false,
      writable: false,
      configurable: false,
      value,
    });

  // 避免重复赋值
  if (d.value !== value) {
    d.value = value;
  }
  return d;
}
// 然后
Object.defineProperty(obj, "key", withValue("static"));
// 如果 freeze 可用,防止添加或删除对象原型属性
// (value、get、set、enumerable、writable、configurable)
(Object.freeze || Object)(Object.prototype);

writable 属性

const o = {}; // 创建一个新对象
Object.defineProperty(o, "a", {
  value: 37,
  writable: false,
});

console.log(o.a); // 37
o.a = 25; // 不会抛出错误
// (在严格模式下,即使值相同,它也会抛出异常)
console.log(o.a); // 37
// 赋值不会成功

// 严格模式
(() => {
  "use strict";
  const o = {};
  Object.defineProperty(o, "b", {
    value: 2,
    writable: false,
  });
  o.b = 3; // 抛出 TypeError: "b" is read-only
  return o.b; // 如果没有上一行的话,会返回 2
})();

enumerable 属性

const o = {};
Object.defineProperty(o, "a", {
  value: 1,
  enumerable: true,
});
Object.defineProperty(o, "b", {
  value: 2,
  enumerable: false,
});
Object.defineProperty(o, "c", {
  value: 3,
});
// 通过赋值创建属性时 enumerable 默认为 true
o.d = 4;
Object.defineProperty(o, Symbol.for("e"), {
  value: 5,
  enumerable: true,
});
Object.defineProperty(o, Symbol.for("f"), {
  value: 6,
  enumerable: false,
});

for (const i in o) {
  console.log(i);
}
// 打印 'a' 和 'd'(总是按这个顺序)

Object.keys(o); // ['a', 'd']
o.propertyIsEnumerable("a"); // true
o.propertyIsEnumerable("b"); // false
o.propertyIsEnumerable("c"); // false
o.propertyIsEnumerable("d"); // true
o.propertyIsEnumerable(Symbol.for("e")); // true
o.propertyIsEnumerable(Symbol.for("f")); // false
const p = { ...o };
p.a; // 1
p.b; // undefined
p.c; // undefined
p.d; // 4
p[Symbol.for("e")]; // 5
p[Symbol.for("f")]; // undefined

configurable 属性

const o = {};
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
  configurable: false,
});

Object.defineProperty(o, "a", {
  configurable: true,
}); // 抛出 TypeError
Object.defineProperty(o, "a", {
  enumerable: true,
}); // 抛出 TypeError
Object.defineProperty(o, "a", {
  set() {},
}); // 抛出 TypeError(set 在之前未定义)
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
}); // 抛出 TypeError
// 即使新的 get 做的事情完全相同
Object.defineProperty(o, "a", {
  value: 12,
}); // 抛出 TypeError
// value 只有在 writable 为 true 时才可以被修改

console.log(o.a); // 1
delete o.a; // 无法删除;严格模式下会抛出错误
console.log(o.a); // 1

// 一个不可配置但可写的数据属性。该属性的 value 仍然可以被更改,writable 也仍然可以从 true 切换到 false
const o = {};
Object.defineProperty(o, "b", {
  writable: true,
  configurable: false,
});
console.log(o.b); // undefined
Object.defineProperty(o, "b", {
  value: 1,
}); // 即使 configurable 为 false,因为对象是可写的,我们仍然可以替换属性的值。
console.log(o.b); // 1
o.b = 2; // 我们也可以使用赋值运算符来更改属性的值
console.log(o.b); // 2
// 切换属性的可写性
Object.defineProperty(o, "b", {
  writable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: because the property is neither writable nor configurable, it cannot be modified
// 此时,无法再次修改属性 'b' 或者恢复它的可写性。

// 一个可配置但不可写的数据属性。该属性的 value 仍然可以使用 defineProperty 进行替换(但不能使用赋值运算符),并且 writable 特性仍然可以切换
const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: true,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // 我们可以使用 defineProperty 方法替换属性的值
console.log(o.b); // 1
o.b = 2; // 在严格模式下抛出 TypeError:cannot change a non-writable property's value with assignment
// 一个不可配置且不可写的数据属性。无法更新该属性的任何特性,包括它的 value 值
const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: the property cannot be modified because it is neither writable nor configurable.

自定义 setters 和 getters

function Archiver() {
  let temperature = null;
  const archive = [];

  Object.defineProperty(this, "temperature", {
    get() {
      console.log("get!");
      return temperature;
    },
    set(value) {
      temperature = value;
      archive.push({ val: temperature });
    },
  });

  this.getArchive = () => archive;
}

const arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
// getter 总是会返回一个相同的值
const pattern = {
  get() {
    return "我总是返回这个字符串,无论你的赋值是什么";
  },
  set() {
    this.myname = "这是我名称的字符串";
  },
};

function TestDefineSetAndGet() {
  Object.defineProperty(this, "myproperty", pattern);
}

const instance = new TestDefineSetAndGet();
instance.myproperty = "test";
console.log(instance.myproperty);
// 我总是返回这个字符串,无论你的赋值是什么

console.log(instance.myname); // 这是我名称的字符串
function MyClass() {}

let value;
Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return value;
  },
  set(x) {
    value = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // 1

function MyClass() {}

Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return this.storedX;
  },
  set(x) {
    this.storedX = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // undefined

function MyClass() {}

MyClass.prototype.x = 1;
Object.defineProperty(MyClass.prototype, "y", {
  writable: false,
  value: 1,
});

const a = new MyClass();
a.x = 2;
console.log(a.x); // 2
console.log(MyClass.prototype.x); // 1
a.y = 2; // 没有作用;严格模式下会报错
console.log(a.y); // 1
console.log(MyClass.prototype.y); // 1

注意

  • 直接赋值默认值都为 true

  • 默认情况下,它们不可枚举、不可配置、不可写

    const o = {};
    
    o.a = 1;
    // 等价于:
    Object.defineProperty(o, "a", {
      value: 1,
      writable: true,
      configurable: true,
      enumerable: true,
    });
    
    // 另一种情况
    Object.defineProperty(o, "a", { value: 1 });
    // 等价于:
    Object.defineProperty(o, "a", {
      value: 1,
      writable: false,
      configurable: false,
      enumerable: false,
    });
    
  • 这些描述符可以继承

    保留自身这些默认值

    • 明确指定所有的属性描述符,冻结 Object.prototype
    • 通过 Object.create(null) 将 proto 属性指向 null
  • 数据描述符和存取描述符不能同时设置

Object.defineProperties()

Object.defineProperties(obj, props)  静态方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

const obj = {};
Object.defineProperties(obj, {
  property1: {
    value: true,
    writable: true,
  },
  property2: {
    value: "Hello",
    writable: false,
  },
});