Object.defineProperty()、Object.defineProperties()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符。
 
返回修改好的对象。
注意:在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定义key为Symbol的属性的方法之一。
 
该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for...in 或 Object.keys 方法), 这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用Object.defineProperty()添加的属性值是不可变的。
 
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
1数据描述符和存取描述符均具有以下可选键值:
configurable(修改descriptor,可以delete删除)
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable(可枚举)
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
 
2数据描述符同时具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable(可赋值)
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
 
3存取描述符同时具有以下可选键值:
get
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
set
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
 
如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结 Object.prototype,明确指定所有的选项,或者将__proto__属性指向null。
// 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);

// 显式
Object.defineProperty(obj, "key", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static"
});

// 循环使用同一对象
function withValue(value) {
  var d = withValue.d || (
    withValue.d = {
      enumerable: false,
      writable: false,
      configurable: false,
      value: null
    }
  );
  d.value = value;
  return d;
}
// ... 并且 ...
Object.defineProperty(obj, "key", withValue("static"));

// 如果 freeze 可用, 防止代码添加或删除对象原型的属性
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);

为了避免这几个属性可能会因为继承来的值而发生改变,最简单的方法就是每次写descriptor的时候把四个属性都显示指定。

创建属性
var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象o拥有了属性a,值为37

// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

o.b = 38;
// 对象o拥有了属性b,值为38

// o.b的值现在总是与bValue相同,除非重新定义o.b

// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
  value: 0x9f91102, 
  get: function() { 
    return 0xdeadbeef; 
  } 
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
修改属性
Writable 属性
var o = {}; // Creates a new object

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

console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.

// strict mode
(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
}());
Enumerable 特性
enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。
var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable:true });
Object.defineProperty(o, "b", { value : 2, enumerable:false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable defaults to false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则这个属性的enumerable为true

for (var i in o) {    
  console.log(i);  
}
// 打印 'a' 和 'd' (in undefined order)

Object.keys(o); // ["a", "d"]

o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
Configurable 特性
configurable特性表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。
var o = {};
Object.defineProperty(o, "a", { get : function(){return 1;}, 
                                configurable : false } );

// throws a TypeError
Object.defineProperty(o, "a", {configurable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {enumerable : true}); 
// throws a TypeError (set was undefined previously) 
Object.defineProperty(o, "a", {set : function(){}}); 
// throws a TypeError (even though the new get does exactly the same thing) 
Object.defineProperty(o, "a", {get : function(){return 1;}});
// throws a TypeError
Object.defineProperty(o, "a", {value : 12});

console.log(o.a); // logs 1
delete o.a; // Nothing happens
console.log(o.a); // logs 1
如果o.a的configurable属性为true,则不会抛出任何错误,并且该属性将在最后被删除。
点运算符添加属性
使用点运算符和Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不同的
var o = {};

o.a = 1;
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});


// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : false,
  configurable : false,
  enumerable : false
});
Internet Explorer 8 实现了 Object.defineProperty() 方法,但 只能在 DOM 对象上使用。
Object.defineProperties()
Object.defineProperties()方法在对象上定义或修改多个属性,然后返回该对象。
Object.defineProperties(obj, props)

下面是例子:

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // etc. etc.
});

polyfill

function defineProperties(obj, properties) {
    function convertToDescriptor(desc) {
      function hasProperty(obj, prop) {//hasOwnProperty没有被保护,用call调用确保正确
        return Object.prototype.hasOwnProperty.call(obj, prop);
      }
  
      function isCallable(v) {//判断get和set属性是否是函数类型
        // NB: modify as necessary if other values than functions are callable.
        return typeof v === 'function';
      }
  
      if (typeof desc !== 'object' || desc === null)//传进来的属性如果不是对象抛出错误
        throw new TypeError('bad desc');
  
      var d = {};
  
      if (hasProperty(desc, 'enumerable'))//可枚举
        d.enumerable = !!desc.enumerable;//!!转换成布尔值
      if (hasProperty(desc, 'configurable'))//可delete,可修改描述符
        d.configurable = !!desc.configurable;
      if (hasProperty(desc, 'value'))//属性值
        d.value = desc.value;
      if (hasProperty(desc, 'writable'))//可赋值
        d.writable = !!desc.writable;
      if (hasProperty(desc, 'get')) {//get
        var g = desc.get;
  
        if (!isCallable(g) && typeof g !== 'undefined')//如果存在get属性但是不是函数,就抛出错误
          throw new TypeError('bad get');
        d.get = g;
      }
      if (hasProperty(desc, 'set')) {
        var s = desc.set;
        if (!isCallable(s) && typeof s !== 'undefined')//如果存在set属性但是不是函数,就抛出错误
          throw new TypeError('bad set');
        d.set = s;
      }
  
      if (('get' in d || 'set' in d) && ('value' in d || 'writable' in d))//判断数据描述符和访问器描述符同时存在就报错
        throw new TypeError('identity-confused descriptor');
  
      return d;//返回 获取到的属性描述符
    }
  
    if (typeof obj !== 'object' || obj === null)//obj不是对象抛出错误
      throw new TypeError('bad obj');
  
    properties = Object(properties);
  
    var keys = Object.keys(properties);
    var descs = [];
  
    for (var i = 0; i < keys.length; i++)
      descs.push([keys[i], convertToDescriptor(properties[keys[i]])]);//属性名和对应属性描述符存成二维数组
  
    for (var i = 0; i < descs.length; i++)
      Object.defineProperty(obj, descs[i][0], descs[i][1]);//循环二维数组,在obj上定义所有属性
  
    return obj;
  }

 

posted @ 2018-06-29 20:21  hahazexia  阅读(677)  评论(0)    收藏  举报