Object.defineProperty()

164 阅读4分钟

看Vue的双向绑定原理的时候,遇到了Object.defineProperty(),虽然有过一点了解,但还不是很清晰,趁着不是很忙,就梳理了一下。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty()的语法是Object.defineProperty(obj,prop,descriptor),接收3个参数:

  • obj:需要改变的对象
  • prop:需要改变的对象的属性
  • descriptor:将被定义或修改的属性描述符。

objprop就很好理解了,主要是descriptor属性描述符不太好理解。

descriptor

descriptor分为两种:并且必须是两种形式之一,不能同时存在。

  • 数据描述符:具有值的属性,改值可能是可写的,也可能不是可写的。
  • 存取描述符:有getter/setter函数对描述的属性。

两种属性的可选键值有:

configurable enumerable value writable get set
数据描述符 yes yes yes yes no no
存取描述符 yes yes no no yes yes

从上面的表格可以看出,如果有value或者writable就是数据描述符,如果有get/set就是存取描述符。

属性值的理解

  • configurable:值为true时,该属性才能被改变,并且能被删除。默认为false
  • enumerable:值为true时,属性才能够出现在对象的枚举属性中,并且新添加的属性才能显示在对象中。默认为false
  • value:属性的值。默认为undefined
  • writable:值为true时,value才能被赋值运算符改变。默认为false。
  • get:一个属性提供getter的方法,如果没有getter则为undefined。当访问该属性是,该方法会被执行。默认为undefined
  • set:一个给属性提供setter的方法,如果没有setter则为undefined。默认为undefined。

obj={a:1} 此时obj.a会有默认的{ value: 1, writable: true, enumerable: true, configurable: true }描述符,如果想要查看某个对象的属性有哪些描述符,可以使用Object.getOwnPropertyDescriptor(obj, prop)查看。 Object.getOwnPropertyDescriptor(obj, prop):该方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

enumerable

var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  enumerable: false //false
});
console.log("obj.a", obj.a); //1
console.log("obj", obj); // {},此时a是不可枚举的,因此在获取obj的时候是没有办法看到a这个属性的
var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  enumerable: true //true
});
console.log("obj.a", obj.a); //1
console.log("obj", obj); // {a:1}

⚠️不能对同一个对象的同一个属性使用两次Object.defineProperty(),否则会报错(当仅设置value并且value的值相同或者什么都不设置的时候除外)

//正确,如果是设置相同的属性并且属性值相同都正确,get/set除外
var obj = {};
Object.defineProperty(obj, "a", {
  value: 1
});
Object.defineProperty(obj, "a", {
  value: 1
});

//错误,如果设置相同属性不同属性值也会报错
Object.defineProperty(obj, "a", {
  value: 1
});
Object.defineProperty(obj, "a", {
  value: 2
});

//正确
Object.defineProperty(obj, "a", {});
Object.defineProperty(obj, "a", {});

//其他情况均报错
//Cannot redefine property: a

writable与configurable
writable为true的时候可以对属性使用赋值运算赋值,为false的时候不可以;而configurable为true的时候改属性才能修改并且被删除,为false的时候不可以。 这样看起来writable与configurable为true的时候都可以更改属性值,貌似作用一样,那又有什么区别呢,看例子:
1、writable:true,configurable:false:可以更改值,但是不能删除

var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  writable: true,
  configurable: false
});
console.log(obj.a); // 1
obj.a = 2;
console.log(obj.a); // 2
delete obj.a;
console.log(obj.a); // 2

2、writable:true,configurable:true:可以更改值,可以删除

var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  writable: true,
  configurable: true
});
console.log(obj.a); //1
obj.a = 2;
console.log(obj.a); //2
delete obj.a;
console.log(obj.a); //undefined

3、writable:false,configurable:true:不可以更改值,可以删除

var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  writable: false,
  configurable: true
});
console.log(obj.a); //1
obj.a = 2;
console.log(obj.a); //1
delete obj.a;
console.log(obj.a); //undefined

4、writable:false,configurable:false:不可以更改值,不可以删除

var obj = {};
Object.defineProperty(obj, "a", {
  value: 1,
  writable: false,
  configurable: false
});
console.log(obj.a); //1
obj.a = 2;
console.log(obj.a); //1
delete obj.a;
console.log(obj.a); //1

由以上可以看出:writable控制的是属性值是否可以更改,configurable控制的是属性是否可以被删除

get/set get:取值 set:设置值

var obj = {};
Object.defineProperty(obj, "a", {
  //   value: 1,
  get: function() {
    console.log("在这里获取值");
  },
  set: function() {
    console.log("在这里设置值");
  }
  //   configurable: false
});
obj.a = 2;
console.log(obj.a);

//打印结果是
在这里设置值
在这里获取值
2

⚠️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

如果不想被这样修改,可以使用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(a.x); // 1
console.log(b.x); // undefined
console.log(myclass.prototype.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; // 
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1