defineProperty

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)用。

語(yǔ)法

Object.defineProperty(obj,prop,descriptor)

參數(shù)

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ǔ)法,可以看看這些額外示例

創(chuàng)建屬性

如果對(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ì)被刪除。

添加多個(gè)屬性和默認(rèn)值

考慮特性被賦予的默認(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

自定義 Setters 和 Getters

下面的例子展示了如何實(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容