Object.defineProperty用法 - JavaScript

Object.defineProperty()为对象添加属性时,会为属性设置对应的get/set方法,做为属性观察者。

使用Object.defineProperty添加的属性,属性值发生变化或被读取时,相应属性的getting/setting方法会被调用。

Vue.js早期使用该方法实现数据双向绑定。

Vue3.0使用Proxy代替Object.defineProperty。

若对兼容性没严格要求,Proxy是很好的解决方案。

Object.defineProperty()

Object.defineProperty(obj, prop, descriptor)

参数说明

obj:任意js对象,属性所属的对象。

prop:属性的名称,可以是新属性,也可以是要修改的属性。

descriptor:属性描述符,定义属性所拥有的特性。

如:能否被赋值、使用for...in遍历时属性能否被看到。

注:descriptor参数是最变化多端的地方,决定了Object.defineProperty的很多特性。

参数:descriptor

descriptor参数定义了属性拥有的特性:

数据属性、访问属性。

两个特性不能在同一个属性上兼而有之。

数据属性

descriptor为数据属性时,descriptor参数拥有一个value属性,value属性的值做为初始化参数,赋给obj.prop,writable属性决定obj.prop能否被重新赋值。

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

示例中,为对象o添加了一个属性a,a的初始值为37。

属性a能够被重新赋值(writable)。

for(var i in o){
  console.log(i)
}

使用for循环时属性a能被遍历出来,可见(enumerable)。

可使用delete操作删除属性a(configurable)。

访问属性

descriptor为访问属性时,对象的属性可拥有getting/setting方法,对属性执行赋值和读取操作时,触发对应方法。

var value = 1,obj = {};

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

示例为obj对象添加属性p,附带了两个代理方法。

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时,允许使用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]);
}

以上两个属性是descriptor参数通用属性。

descriptor为数据属性时有以下可选值:

value

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

writable

该值为true时,属性的值可使用赋值运算符修改,默认:false。

var o = {};

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

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

// strict 模式
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2,
    writable: false
  });
  o.b = 3; // throws TypeError: "b"为只读
  return o.b; // returns 2
}());

descriptor为访问属性时有以下可选值:

get

属性对应的getter方法,属性被访问时,会调用getter方法。默认:undefined。

set

属性对应的setter方法,属性值被修改时,方法被调用,方法接受一个参数,即,属性的新值。默认:undefined。

注意事项

如果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);

继承属性

访问者的属性是被继承,它的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

js的方法与函数在概念上是有明确定义的,本文有意忽略了这一点。