MDN:Object.defineProperty() - JavaScript | MDN (mozilla.org)
Object.defineProperty()
Object.defineProperty()?方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象。
備注:應(yīng)當(dāng)直接在?Object?構(gòu)造器對(duì)象上調(diào)用此方法,而不是在任意一個(gè)?Object?類型的實(shí)例上調(diào)用。
Object.defineProperty(obj,prop,descriptor)
obj
要定義屬性的對(duì)象。
prop
要定義或修改的屬性的名稱或?Symbol?。
descriptor
要定義或修改的屬性描述符。
被傳遞給函數(shù)的對(duì)象。
在ES6中,由于 Symbol類型的特殊性,用Symbol類型的值來(lái)做對(duì)象的key與常規(guī)的定義或修改不同,而Object.defineProperty?是定義key為Symbol的屬性的方法之一。
該方法允許精確地添加或修改對(duì)象的屬性。通過(guò)賦值操作添加的普通屬性是可枚舉的,在枚舉對(duì)象屬性時(shí)會(huì)被枚舉到(for...in?或?Object.keys方法),可以改變這些屬性的值,也可以刪除這些屬性。這個(gè)方法允許修改默認(rèn)的額外選項(xiàng)(或配置)。默認(rèn)情況下,使用?Object.defineProperty()?添加的屬性值是不可修改(immutable)的。
對(duì)象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。數(shù)據(jù)描述符是一個(gè)具有值的屬性,該值可以是可寫的,也可以是不可寫的。存取描述符是由 getter 函數(shù)和 setter 函數(shù)所描述的屬性。一個(gè)描述符只能是這兩者其中之一;不能同時(shí)是兩者。
這兩種描述符都是對(duì)象。它們共享以下可選鍵值(默認(rèn)值是指在使用?Object.defineProperty()?定義屬性時(shí)的默認(rèn)值):
configurable
當(dāng)且僅當(dāng)該屬性的?configurable?鍵值為?true?時(shí),該屬性的描述符才能夠被改變,同時(shí)該屬性也能從對(duì)應(yīng)的對(duì)象上被刪除。
默認(rèn)為?false。
enumerable
當(dāng)且僅當(dāng)該屬性的?enumerable?鍵值為?true?時(shí),該屬性才會(huì)出現(xiàn)在對(duì)象的枚舉屬性中。
默認(rèn)為?false。
數(shù)據(jù)描述符還具有以下可選鍵值:
value
該屬性對(duì)應(yīng)的值??梢允侨魏斡行У?JavaScript 值(數(shù)值,對(duì)象,函數(shù)等)。
默認(rèn)為?undefined。
writable
當(dāng)且僅當(dāng)該屬性的?writable?鍵值為?true?時(shí),屬性的值,也就是上面的?value,才能被賦值運(yùn)算符?(en-US)改變。
默認(rèn)為?false。
存取描述符還具有以下可選鍵值:
get
屬性的 getter 函數(shù),如果沒(méi)有 getter,則為?undefined。當(dāng)訪問(wèn)該屬性時(shí),會(huì)調(diào)用此函數(shù)。執(zhí)行時(shí)不傳入任何參數(shù),但是會(huì)傳入?this?對(duì)象(由于繼承關(guān)系,這里的this并不一定是定義該屬性的對(duì)象)。該函數(shù)的返回值會(huì)被用作屬性的值。
默認(rèn)為?undefined。
set
屬性的 setter 函數(shù),如果沒(méi)有 setter,則為?undefined。當(dāng)屬性值被修改時(shí),會(huì)調(diào)用此函數(shù)。該方法接受一個(gè)參數(shù)(也就是被賦予的新值),會(huì)傳入賦值時(shí)的?this?對(duì)象。
默認(rèn)為?undefined。
描述符默認(rèn)值匯總
擁有布爾值的鍵?configurable、enumerable?和?writable?的默認(rèn)值都是?false。
屬性值和函數(shù)的鍵?value、get?和?set?字段的默認(rèn)值為?undefined。
描述符可擁有的鍵值
configurableenumerablevaluewritablegetset
數(shù)據(jù)描述符可以可以可以可以不可以不可以
存取描述符可以可以不可以不可以可以可以
如果一個(gè)描述符不具有?value、writable、get?和?set?中的任意一個(gè)鍵,那么它將被認(rèn)為是一個(gè)數(shù)據(jù)描述符。如果一個(gè)描述符同時(shí)擁有?value?或?writable?和?get?或?set?鍵,則會(huì)產(chǎn)生一個(gè)異常。
記住,這些選項(xiàng)不一定是自身屬性,也要考慮繼承來(lái)的屬性。為了確認(rèn)保留這些默認(rèn)值,在設(shè)置之前,可能要凍結(jié)?Object.prototype?(en-US),明確指定所有的選項(xiàng),或者通過(guò)?Object.create(null)?將?__proto__?(en-US)?屬性指向?null。
// 使用 __proto__varobj={};vardescriptor=Object.create(null);// 沒(méi)有繼承的屬性// 默認(rèn)沒(méi)有 enumerable,沒(méi)有 configurable,沒(méi)有 writabledescriptor.value='static';Object.defineProperty(obj,'key',descriptor);// 顯式Object.defineProperty(obj,"key",{enumerable:false,configurable:false,writable:false,value:"static"});// 循環(huán)使用同一對(duì)象functionwithValue(value){vard=withValue.d||(withValue.d={enumerable:false,writable:false,configurable:false,value:null});d.value=value;returnd;}// ... 并且 ...Object.defineProperty(obj,"key",withValue("static"));// 如果 freeze 可用, 防止后續(xù)代碼添加或刪除對(duì)象原型的屬性// (value, get, set, enumerable, writable, configurable)(Object.freeze||Object)(Object.prototype);
Copy to Clipboard
如果你想了解如何使用?Object.defineProperty?方法和類二進(jìn)制標(biāo)記語(yǔ)法,可以看看這些額外示例。
如果對(duì)象中不存在指定的屬性,Object.defineProperty()?會(huì)創(chuàng)建這個(gè)屬性。當(dāng)描述符中省略某些字段時(shí),這些字段將使用它們的默認(rèn)值。
varo={};// 創(chuàng)建一個(gè)新對(duì)象// 在對(duì)象中添加一個(gè)屬性與數(shù)據(jù)描述符的示例Object.defineProperty(o,"a",{value:37,writable:true,enumerable:true,configurable:true});// 對(duì)象 o 擁有了屬性 a,值為 37// 在對(duì)象中添加一個(gè)設(shè)置了存取描述符屬性的示例varbValue=38;Object.defineProperty(o,"b",{// 使用了方法名稱縮寫(ES2015 特性)// 下面兩個(gè)縮寫等價(jià)于:// get : function() { return bValue; },// set : function(newValue) { bValue = newValue; },get(){returnbValue;},set(newValue){bValue=newValue;},enumerable:true,configurable:true});o.b;// 38// 對(duì)象 o 擁有了屬性 b,值為 38// 現(xiàn)在,除非重新定義 o.b,o.b 的值總是與 bValue 相同// 數(shù)據(jù)描述符和存取描述符不能混合使用Object.defineProperty(o,"conflict",{value:0x9f91102,get(){return0xdeadbeef;}});// 拋出錯(cuò)誤 TypeError: value appears only in data descriptors, get appears only in accessor descriptors
Copy to Clipboard
如果屬性已經(jīng)存在,Object.defineProperty()將嘗試根據(jù)描述符中的值以及對(duì)象當(dāng)前的配置來(lái)修改這個(gè)屬性。如果舊描述符將其configurable?屬性設(shè)置為false,則該屬性被認(rèn)為是“不可配置的”,并且沒(méi)有屬性可以被改變(除了單向改變 writable 為 false)。當(dāng)屬性不可配置時(shí),不能在數(shù)據(jù)和訪問(wèn)器屬性類型之間切換。
當(dāng)試圖改變不可配置屬性(除了?value?和?writable?屬性之外)的值時(shí),會(huì)拋出TypeError,除非當(dāng)前值和新值相同。
Writable 屬性
當(dāng)?writable?屬性設(shè)置為?false?時(shí),該屬性被稱為“不可寫的”。它不能被重新賦值。
varo={};// 創(chuàng)建一個(gè)新對(duì)象Object.defineProperty(o,'a',{value:37,writable:false});console.log(o.a);// logs 37o.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';varo={};Object.defineProperty(o,'b',{value:2,writable:false});o.b=3;// throws TypeError: "b" is read-onlyreturno.b;// returns 2 without the line above}());
Copy to Clipboard
如示例所示,試圖寫入非可寫屬性不會(huì)改變它,也不會(huì)引發(fā)錯(cuò)誤。
Enumerable 屬性
enumerable?定義了對(duì)象的屬性是否可以在?for...in?循環(huán)和?Object.keys()?中被枚舉。
varo={};Object.defineProperty(o,"a",{value:1,enumerable:true});Object.defineProperty(o,"b",{value:2,enumerable:false});Object.defineProperty(o,"c",{value:3});// enumerable 默認(rèn)為 falseo.d=4;// 如果使用直接賦值的方式創(chuàng)建對(duì)象的屬性,則 enumerable 為 trueObject.defineProperty(o,Symbol.for('e'),{value:5,enumerable:true});Object.defineProperty(o,Symbol.for('f'),{value:6,enumerable:false});for(variino){console.log(i);}// logs 'a' and 'd' (in undefined order)Object.keys(o);// ['a', 'd']o.propertyIsEnumerable('a');// trueo.propertyIsEnumerable('b');// falseo.propertyIsEnumerable('c');// falseo.propertyIsEnumerable('d');// trueo.propertyIsEnumerable(Symbol.for('e'));// trueo.propertyIsEnumerable(Symbol.for('f'));// falsevarp={...o}p.a// 1p.b// undefinedp.c// undefinedp.d// 4p[Symbol.for('e')]// 5p[Symbol.for('f')]// undefined
Copy to Clipboard
Configurable 屬性
configurable?特性表示對(duì)象的屬性是否可以被刪除,以及除?value?和?writable?特性外的其他特性是否可以被修改。
varo={};Object.defineProperty(o,'a',{get(){return1;},configurable:false});Object.defineProperty(o,'a',{configurable:true});// throws a TypeErrorObject.defineProperty(o,'a',{enumerable:true});// throws a TypeErrorObject.defineProperty(o,'a',{set(){}});// throws a TypeError (set was undefined previously)Object.defineProperty(o,'a',{get(){return1;}});// throws a TypeError// (even though the new get does exactly the same thing)Object.defineProperty(o,'a',{value:12});// throws a TypeError // ('value' can be changed when 'configurable' is false but not in this case due to 'get' accessor)console.log(o.a);// logs 1deleteo.a;// Nothing happensconsole.log(o.a);// logs 1
Copy to Clipboard
如果?o.a?的?configurable?屬性為?true,則不會(huì)拋出任何錯(cuò)誤,并且,最后,該屬性會(huì)被刪除。
考慮特性被賦予的默認(rèn)特性值非常重要,通常,使用點(diǎn)運(yùn)算符和?Object.defineProperty()?為對(duì)象的屬性賦值時(shí),數(shù)據(jù)描述符中的屬性默認(rèn)值是不同的,如下例所示。
varo={};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});
Copy to Clipboard
下面的例子展示了如何實(shí)現(xiàn)一個(gè)自存檔對(duì)象。當(dāng)設(shè)置temperature?屬性時(shí),archive?數(shù)組會(huì)收到日志條目。
functionArchiver(){vartemperature=null;vararchive=[];Object.defineProperty(this,'temperature',{get:function(){console.log('get!');returntemperature;},set:function(value){temperature=value;archive.push({val:temperature});}});this.getArchive=function(){returnarchive;};}vararc=newArchiver();arc.temperature;// 'get!'arc.temperature=11;arc.temperature=13;arc.getArchive();// [{ val: 11 }, { val: 13 }]
Copy to Clipboard
下面這個(gè)例子中,getter 總是會(huì)返回一個(gè)相同的值。
varpattern={get:function(){return'I alway return this string,whatever you have assigned';},set:function(){this.myname='this is my name string';}};functionTestDefineSetAndGet(){Object.defineProperty(this,'myproperty',pattern);}varinstance=newTestDefineSetAndGet();instance.myproperty='test';// 'I alway return this string,whatever you have assigned'console.log(instance.myproperty);// 'this is my name string'console.log(instance.myname);
Copy to Clipboard
如果訪問(wèn)者的屬性是被繼承的,它的?get?和?set?方法會(huì)在子對(duì)象的屬性被訪問(wèn)或者修改時(shí)被調(diào)用。如果這些方法用一個(gè)變量存值,該值會(huì)被所有對(duì)象共享。
functionmyclass(){}varvalue;Object.defineProperty(myclass.prototype,"x",{get(){returnvalue;},set(x){value=x;}});vara=newmyclass();varb=newmyclass();a.x=1;console.log(b.x);// 1
Copy to Clipboard
這可以通過(guò)將值存儲(chǔ)在另一個(gè)屬性中解決。在?get?和?set?方法中,this?指向某個(gè)被訪問(wèn)和修改屬性的對(duì)象。
functionmyclass(){}Object.defineProperty(myclass.prototype,"x",{get(){returnthis.stored_x;},set(x){this.stored_x=x;}});vara=newmyclass();varb=newmyclass();a.x=1;console.log(b.x);// undefined
Copy to Clipboard
不像訪問(wèn)者屬性,值屬性始終在對(duì)象自身上設(shè)置,而不是一個(gè)原型。然而,如果一個(gè)不可寫的屬性被繼承,它仍然可以防止修改對(duì)象的屬性。
functionmyclass(){}myclass.prototype.x=1;Object.defineProperty(myclass.prototype,"y",{writable:false,value:1});vara=newmyclass();a.x=2;console.log(a.x);// 2console.log(myclass.prototype.x);// 1a.y=2;// Ignored, throws in strict modeconsole.log(a.y);// 1console.log(myclass.prototype.y);// 1