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; }