Javascript: Object.defineProperty

198 阅读3分钟

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

Object.defineProperty(object, propName, descriptor)

参数

object 要定义属性的对象
propName 要定义或修改的属性的名称或 Symbol
descriptor 要定义或修改的属性描述符

返回值

被传递给函数的对象

描述

数据描述符
value: 该属性对应的值,可以是任何有效的Javascript值,包括数值对象函数。默认值为undefined
writable:是否可修改。默认值为false
enumerable:是否可遍历枚举。默认值为false
configurable:是否可修改配置项。当值为true时,该属性的描述符才能被改变,同时该属性也能从对应的对象上被删除。默认值为false

const object = {};
Object.defineProperty(object, 'name', {
  value: 'test',  
  writable: false,
  enumerable: false,
  configurable: false
})

存取描述符
get: 当访问对象属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承的关系,这里的this并不一定是定义该属性的对象)。返回值会被用作属性的值。默认为undefined
set: 属性值被修改时,会调用此函数。该方法接受一个参数(即被赋予的新值),会传入赋值时的this对象。默认为undefined

let value = 'test';
const object = {};
Object.defineProperty(object, 'name', {
  get() {return value},
  set(newValue) {value = newValue}
})

如果一个描述符不具有valuewritablegetset中的任意一个键,那么它将被认为是一个数据描述符
如果一个描述符同时拥有valuewritablegetset键,则会产生一个异常

// 同时存在writable、get和set键
let value = 'test';
const object = {};
Object.defineProperty(object, 'name', {
  get() {return value},
  set(newValue) {value = newValue},
  writable: true,  
  enumerable: false, 
  configurable: false 
})

// 报异常 TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

属性示例

writable属性

const object = {};
Object.defineProperty(object, 'name', {
  value: 'aa',
  writable: false,  
});

object.name = 'kk';  // 修改无效,仅当writable属性值为true时才可修改
console.log(object.name) // 输出aa

enumerable属性

const object = {};
Object.defineProperty(object, 'name', {
  value: 'aa',
  enumerable: false,  
});

console.log(Object.keys(object));  // 输出[]。仅当enumerable属性值为true时输出['name']

configurable属性

// 属性值为false时
const object = {};
Object.defineProperty(object, 'name', {
  value: 'aa',
  configurable: false,  
});

Object.defineProperty(object, 'name', {
  writable: true,  
});

// 报异常 TypeError: Cannot redefine property: name
// 属性值为true时
const object = {};
Object.defineProperty(object, 'name', {
  value: 'aa',
  configurable: true,  
});

object.name = 'kk';
console.log(object);  // 输出{name: 'aa'}

Object.defineProperty(object, 'name', {
  writable: true,  
});

object.name = 'kk';
console.log(object);  // 输出{name: 'kk'}

属性默认值

const object = {};
object.name = 'aa';

// 等同于 =>
Object.defineProperty(object, 'name', {
  value: 'aa',
  writable: true,
  enumerable: true,
  configurable: true
});
Object.defineProperty(object, 'name', {
  value: 'aa'
});

// 等同于 =>
Object.defineProperty(object, 'name', {
  value: 'aa',
  writable: false,
  enumerable: false,
  configurable: false
})

自定义getterssetters

function Archiver() {
  let name = null;
  let archive = [];
  
  Object.defineProperty(this, 'name', {
    get() {
      console.log('get');
      return name;
    },
    set(value) {
      name = value;
      archive.push({val: name});
    }
  })
  
  this.getArchive = () => archive;
}

const arc = new Archiver();
arc.name;  // get
arc.name = 'aa';
arc.name = 'bb';
arc.getArchive();  // [{val: 'aa'}, {val: 'bb'}]

继承属性

如果访问者的属性是被继承的,它的getset方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。

function MyClass() {}

let value;
Object.defineProperty(MyClass.prototype, 'name', {
  get() {return value},
  set(val) {value = val}
});

const a = new MyClass();
const b = new MyClass();
a.name = 'aa';
console.log(a.name); // aa
console.log(b.name); // aa

可以通过将值储存在另一个属性中解决。在getset方法中,this指向某个被访问和修改属性的对象。

function MyClass() {}

let value;
Object.defineProperty(MyClass.prototype, 'name', {
  get() {return this.value},
  set(val) {this.value = val}
});

const a = new MyClass();
const b = new MyClass();
a.name = 'aa';
console.log(a.name); // aa
console.log(b.name); // undefined

image.png

如果一个不可写的属性被继承时,仍然可以防止修改对象的属性。

function MyClass() {}

MyClass.prototype.name = 'aa';
Object.defineProperty(MyClass.prototype, 'age', {
  value: 20,
  writable: false
})

const a = new MyClass();
a.name = 'kk';
console.log(a.name);  // 输出kk
console.log(MyClass.prototype.name);  // 输出aa

a.age = 25;
console.log(a.age);   // 输出20
console.log(MyClass.prototype.age);  // 输出20

参考资料:[MDN](Object.defineProperty() - JavaScript | MDN (mozilla.org))