Object.defineProperty函数用法-JavaScript

Object.defineProperty()函数在为对象添加新属性时,会为属性创建对应的get/set方法,并监听属性的变化,vus.js利用这一特性构建出时下流行的前端框架。

vue.js使用Object.defineProperty函数为对象添加属性,并为之生成对应的getter/setter方法,对这些属性进行赋值操作时,会触发属性对应的set方法;读取属性时,会触及对应的get方法。

Object.defineProperty函数

Object.defineProperty(obj, prop, descriptor)

参数说明:

obj:需要添加或修改属性的对象,任意js对象。

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

descriptor:属性描述符,定义了属性所拥有的特性。如:能否被赋值、使用for...in遍历属性时能否被看到。

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

descriptor参数

descriptor参数定义了属性所能拥有的特性:数据属性、访问属性,这两个特性在同一个属性上不可兼而有之。

当属性为数据属性时,descriptor参数是一个有value属性的对象,value的值做为初始化参数赋给属性,writable参数决定了属性能否被重新赋值。

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

示例中,为对象o添加了一个属性a,a的初始值为37,可以被重新赋值(writable),对for(var i in o){console.log(i)}可见(enumerable),可通过delete操作删除属性a(configurable)。

当descriptor为访问属性时,对象的属性可以拥有一对get/set方法,当对属性进行赋值和读取操作时,会触发这对方法。

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时,对应的属性能够被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); //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参数注意事项

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

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