ES5定义的Object.defineProperty()方法提供了面向对象实现中 『属性』的概念,类似于C#的属性(Property),或Java的访问器(Accessor)。 『属性』可以用来隐藏内部变量,实现写保护和读写钩子,从而加强对象封装。 ES5为此给出了一系列的对象属性管理方法,包括:Object.defineProperty, Object.preventExtensions, Object.seal, Object.freeze等。

defineProperty参数

Object.defineProperty(obj, prop, descriptor)用于在对象obj上添加(或修改) 名为prop的属性,该方法接受三个参数:

  • obj:需要定义属性的对象
  • prop:属性名,字符串类型
  • descriptor:属性描述符,对象类型。

可以通过descriptor可以精确地控制该属性的行为, 该描述符可以分为数据描述符存取描述符两种。它们具有不同的属性,不可混用。

数据描述符类似于强类型编程语言中的属性修饰符,这种描述符可以有下列属性:

  • value:属性值,默认为undefined
  • writable:为true时才可被赋值运算符改变,默认为false

存取描述符类似于其他面向对象语言中的访问器,可以有下列属性:

  • get:读访问器,返回值即为属性的值。默认为undefined
  • set:写访问器,该方法接受一个参数作为新的值,一般会将该值保存下来一共读访问器使用。默认为undefined

两种描述符都具有的属性包括:

  • configurable:为true时该属性才可被配置和删除,默认为false
  • enumerable:为true时该属性才能出现在对象属性枚举中,默认为false

数据描述符示例

使用writable属性可禁止属性的值被赋值运算符更改。

var post = {};
Object.defineProperty(post, "author", { 
    value : 'harttle',
    writable : false 
});

console.log(post.author); // harttle
post.author = 'another';  // 只在严格模式会抛出错误
console.log(post.author); // harttle,赋值不起作用

如果configurabletrue,该属性还是可以被Object.defineProperty()更改的:

var post = {};
Object.defineProperty(post, "author", { 
    value : 'harttle',
    writable : false,
    configurable: true
});
Object.defineProperty(post, "author", { 
    value : 'another',
});

post.author = 'another';
console.log(post.author); // another

enumerable定义了对象属性是否可被枚举。值为false时,for...in将不会遍历到该属性。

存取描述符示例

该示例来自MDN

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

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

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

禁止属性扩展

有些情况下我们希望禁止客户通过defineProperty()进行属性扩展, ES5提供了三级的限制:

  • Object.preventExtensions(obj): 禁止新的属性被添加到obj中,直接的属性赋值也会失效。可通过Object.isExtensible(obj)来检查。
  • Object.seal(obj): 同上,属性也将不可被删除。等效于为所有属性设置configurable: false。可通过Object.isSealed(obj)来检查。
  • Object.freeze(obj): 同上,属性也将不可被改变。等效于为所有属性设置writable: false。可通过Object.isFrozen(obj)来检查。

注意上述三个方法都是浅的(Shallow),即对象属性的isExtensible, isSealed, isFrozen不会发生变化。

参考阅读

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2016/07/26/es5-define-properties.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。