Object.defineProperty函数用法示例-JavaScript

Object.defineProperty()方法能为指定的对象添加新属性和get/set方法,也可以修改指定对象的属性,并返回当前对象。

vue.js利用Object.defineProperty函数在前端实现数据双向绑定,让其迅速成为关注的焦点。在实现这项特性时,利用Object.defineProperty函数为对象添加属性及对应的get/set方法,当通过赋值运算符修改对象的属性时,会触发属性对应的set方法被执行,当读取对象属性时,属性对应的get方法会被调用。

Object.defineProperty API说明

Object.defineProperty(obj, prop, descriptor)

obj为需要添加或修改属性的对象,可以是任意对象。

prop为属性的名称,可以是新添加的属性名,也可以是需要修改的属性名。

descriptor为属性描述符,定义了属性所拥有的特性,如:能否被赋值、能否使用for...in遍历到此属性。descriptor参数也是Object.defineProperty函数最变化多端的地方。

Object.defineProperty函数descriptor参数

Object.defineProperty函数的属性描述符有两种形式:数据描述符和访问描述符。两种形式在同一个属性上不可兼而有之。

当descriptor为数据描述时,它是一个有value属性的对象,value可以被覆写,也可以不被覆写。

Object.defineProperty(o, "a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

当descriptor为访问描述符时,可以为目标对象的属性设置getter-setter方法,此时访问描述符用来控制这对函数的特性。

var value = 1;

Object.defineProperty(o, "b", {
  get : function(){
    return value;
  },
  set : function(newValue){
    value = newValue;
  },
  enumerable : true,
  configurable : true
});

Object.defineProperty函数descriptor参数属性

configurable

当该属性为true时描述符能够被改变,就是说描述符可以在两种形式之间自由切换,同时意味对象上的属性能否被删除,除value和writable特性外的其他特性是否能被修改;默认为false。

var obj = {},value = 12;

var nobj = Object.defineProperty(obj, "b", {
  get : function(){
    return value;
  },
  set : function(newValue){
      value = newValue;
  },
  enumerable : true,
  configurable : false
});

var ab = Object.defineProperty(obj, 'b', {
  value: 37,
  writable: true
});

enumerable

当该属性为true时,对象上属性能够通过js的枚举方法遍历,意味着可以使用for...in和Object.keys()操作属性;默认为false。

var obj = {};

var ab = Object.defineProperty(obj, 'b', {
  value: 37,
  enumerable : false,
  writable: true
});

for(var p in obj){
    console.log(obj[p]);
}

以上两种属性是Object.defineProperty函数descriptor参数共有的。

当descriptor为数据描述符时有以下可选属性:

value

与属性关联的值,可以是任何符合JavaScript规范的值(数字、对象、函数等);默认为undefined。

writable

当该属性值为true时,关联对象上的值可以使用赋值运算符修改;默认为false。

var o = {};

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // logs 37
o.a = 25;
console.log(o.a); // logs 37

// strict 模式
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2,
    writable: false
  });
  o.b = 3; // throws TypeError: "b" is read-only
  return o.b; // returns 2 without the line above
}());

当descriptor为访问描述符时有以下可选属性:

get

作为属性对应的getter方法,当访问属性时,会调用此函数。vue.js框架正是利用了这一特性。默认为undefined。

set

作为属性对应setter的方法,当属性值被修改时,该方法会被触发执行,该方法将接受唯一参数,即该属性的新值。默认为undefined。

Object.defineProperty函数descriptor参数注意事项

如果Object.defineProperty函数的descriptor参数,不具有value、writable、get和set任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时具有value/writable和get/set关键字,将会出现异常。

有时把一个对象做为descriptor参数传入时,为了防止此对象已继承了一些未知属性,可以使用Object.create(null)方法创建一个无任何继承关系的干净对象。

var obj = {};

var descriptor = Object.create(null); // 没有属性继承的对象
// 默认没有 enumerable、configurable、writable

descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);

Object.defineProperty继承属性

如果访问者的属性是被继承的,它的get和set方法能被子对象所访问。如果这些方法用共享同一个变量,该值会被所有对象共享。

function myclass() {
}

var value;

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

var a = new myclass();
var b = new myclass();

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

可以通过将值存储在私有属性上解决,在get和set方法中,this指向当前被访问和修改属性的对象。

function myclass() {
}

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

var a = new myclass();
var 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
});

var a = new myclass();

a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1

a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1