Object.definedProperty方法能够在一个对象上直接定义一个新的属性、或修改一个对象已经存在的属性,最终返回这个对象。html
Object.defineProperty(obj, prop, descriptor)数组
该方法容许开发者精确的对对象属性的定义和修改。经过正常赋值进行属性添加而构建的属性会被枚举器方法(如for..in循环或Object.keys方法)获取,从而致使属性值被外部方法改变或删除。而Object.defineProperty()能够避免以上描述的状况,经过Object.defineProperty()添加的属性是默认不可改变的。
属性描述参数(descriptor)主要由两部分构成:数据描述符(data descriptor)和访问器描述符(accessor descriptor)。数据描述符就是一个包含属性的值,并说明这个值可读或不可读的对象;访问器描述符就是包含该属性的一对getter-setter方法的对象。一个完整的属性描述(descriptor)必须是这二者之一,而且不能够二者都有。app
configurable
仅当设置的属性的描述符须要被修改或须要经过delete来删除该属性时,configurable属性设置为true。默认为false。this
enumerable
仅当设置的属性须要被枚举器(如for..in)访问时设置为true。默认为false。spa
value
设置属性的值,能够是任何JavaScript值类型(number,object,function等类型)。默认为undefined。prototype
writable
仅当属性的值能够被赋值操做修改时设置为true。默认为false。双向绑定
get
属性的getter方法,若属性没有getter方法则为undefined。该方法的返回为属性的值。默认为undefined。日志
set
属性的setter方法,若属性没有setter方法则为undefined。该方法接收惟一的参数,做为属性的新值。默认为undefined。code
请牢记,这些描述符的属性并非必须的,从原型链继承而来的属性也可填充。为了保证这些描述符属性被填充为默认值,你可能会使用形如预先冻结Object.prototype、明确设置每一个描述符属性的值、使用Object.create(null)来获取空对象等方式。htm
一个简单的例子:
1 var obj = {}; 2 var descriptor = Object.create(null); // no inherited properties 3 4 //全部描述符的属性被设置为默认值 5 descriptor.value = 'static'; 6 Object.defineProperty(obj, 'key', descriptor); 7 8 //明确设置每一个描述符的属性 9 Object.defineProperty(obj, 'key', { 10 enumerable: false, 11 configurable: false, 12 writable: false, 13 value: 'static' 14 }); 15 16 //重用同一个对象做为描述符 17 function withValue(value) { 18 var d = withValue.d || ( 19 withValue.d = { 20 enumerable: false, 21 writable: false, 22 configurable: false, 23 value: null 24 } 25 ); 26 d.value = value; 27 return d; 28 } 29 Object.defineProperty(obj, 'key', withValue('static')); 30 31 //若是Object.freeze方法可用,则使用它来防止对对象属性的修改 32 (Object.freeze || Object)(Object.prototype);
若是当前对象不存在咱们要设置的属性,Object.defineProperty()会根据方法设置为对象建立一个新的属性。若是描述符参数缺失,则会被设置为默认值。全部布尔型描述符属性会被默认设置为false。而value,get,set会被默认设置为undefined。一个未设置get/set/value/writable的属性被称为一个“原生属性(generic)”,而且他的描述符(descriptor)会被“归类”为一个数据描述符(data descriptor)
var o = {}; //建立一个对象 //使用数据描述符来为对象添加属性 Object.defineProperty(o, 'a', { value: 37, writable: true, enumerable: true, configurable: true }); //属性”a”被设置到对象o上,而且值为37 //使用访问器描述符来为对象添加属性 var bValue = 38; Object.defineProperty(o, 'b', { get: function() { return bValue; }, set: function(newValue) { bValue = newValue; }, enumerable: true, configurable: true }); o.b; // 38 //属性”b”被设置到对象o上,而且值为38。 //如今o.b的值指向bValue变量,除非o.b被从新定义 //你不能尝试混合数据、访问器两种描述符 Object.defineProperty(o, 'conflict', { value: 0x9f91102, get: function() { return 0xdeadbeef; } }); //抛出一个类型错误: value appears only in data descriptors, get appears only in accessor descriptors(value只出如今数据描述符中,get只出如今访问器描述符中)
当某个属性已经存在了,Object.defineProperty()会根据对象的属性配置(configuration)和新设置的值来尝试修改该属性。若是该属性的configurable被设置为false,则该属性没法被修改(这种状况下有个特殊状况:若是以前的writable设置为true,则咱们仍能够将writable设置为false,一旦这么作以后,任何描述符属性将变得不可设置)。若是属性的configurable设置为false,则咱们没法将属性的描述符在数据描述符和访问器描述符之间转换。
若是新设置的属性和该属性不一样,而且该属性的configurable被设置为false,则一个类型错误(TypeError)会被抛出(除了上一段文字中说的特殊状况)。若新旧属性彻底相同,则什么都不会发生。
当一个属性的writable被设置为false,这个属性就成为“不可写的(non-writable)”。该属性不可被从新赋值。
var o = {}; //建立一个对象 Object.defineProperty(o, 'a', { value: 37, writable: false }); console.log(o.a); // 37 o.a = 25; //没有错误抛出 //在严格模式下会抛出错误 console.log(o.a); //仍然是37,赋值操做无效
正如上述代码所述,尝试重写一个“不可写(non-writable)”属性不会发生任何改变,也不会抛出错误。
属性的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默认设置为false o.d = 4; //经过直接设置属性的方式,enumerable将被设置为true for (var i in o) { console.log(i); } //打印出’a’和’d’ Object.keys(o); // ['a', 'd'] o.propertyIsEnumerable('a'); // true o.propertyIsEnumerable('b'); // false o.propertyIsEnumerable('c'); // false
属性的configurable值控制一个对象的属性能否被delete删除,同时也控制该属性描述符的配置能否改变(除了前文所述在configurable为false时,若writable为true,则仍能够进行一次修改将writable改变为false)。
var o = {}; Object.defineProperty(o, 'a', { get: function() { return 1; }, configurable: false }); Object.defineProperty(o, 'a', { configurable: true }); //抛出错误 Object.defineProperty(o, 'a', { enumerable: true }); //抛出错误 Object.defineProperty(o, 'a', { set: function() {} }); //抛出错误(set以前被设置为undefined) Object.defineProperty(o, 'a', { get: function() { return 1; } }); //抛出错误(即便新的get作的是相同的事,但方法的先后引用不相同) Object.defineProperty(o, 'a', { value: 12 }); //抛出错误 console.log(o.a); // 1 delete o.a; //什么都不发生 console.log(o.a); // 1
若是o.a属性的configurable为true,就不会有任何错误抛出,而且o.a在最后的delete操做中会被删除。
考虑描述符特性的默认值如何被应用是很是重要的。正以下面示例所示,简单的使用”.”符号来设置一个属性和使用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 });
下面的示例展现了如何实现一个“自存档(self-archiving)”的对象。当temperature属性被设置时,archive数组就会添加一个日志记录。
function Archiver() { var temperature = null; var archive = []; Object.defineProperty(this, 'temperature', { get: function() { console.log('get!'); return temperature; }, set: function(value) { temperature = value; archive.push({ val: temperature }); } }); this.getArchive = function() { return archive; }; } var arc = new Archiver(); arc.temperature; // 'get!' arc.temperature = 11; arc.temperature = 13; arc.getArchive(); // [{ val: 11 }, { val: 13 }]
或者下面这样写也是一样的效果:
var pattern = { get: function () { return 'I always return this string, whatever you have assigned'; }, set: function () { this.myname = 'this is my name string'; } }; function TestDefineSetAndGet() { Object.defineProperty(this, 'myproperty', pattern); } var instance = new TestDefineSetAndGet(); instance.myproperty = 'test'; console.log(instance.myproperty); // I always return this string, whatever you have assigned console.log(instance.myname); // this is my name string
最后放上一个利用Object.defineProperty()实现的简单的双向绑定的例子
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Document</title> </head> <body> <input type="text" id="aa"/> <span id="bb">{{hello}}</span> <script> var obj = {}; Object.defineProperty(obj,'hello',{ set:function(val){ document.querySelector('#bb').innerHTML = val; document.querySelector('#aa').value = val; } }); document.querySelector('#aa').oninput = function(e){ obj.hello = e.target.value; }; obj.hello = ""; obj.hello = "abc"; </script> </body> </html>