[JS] JavaScript 物件(Object) | PJCHENder 未整理筆記

文章推薦指數: 80 %
投票人數:10人

[JS] JavaScript 物件(Object). Property flags and descriptors @ javascript.info; Object @ MDN > Web technology for developers > JavaScript ... Skiptomaincontent這個網站放置的是未發佈或未完整整理的筆記內容,若想檢視正式的筆記內容請到PJCHENder那些沒告訴你的小細節。

PJCHENderOfficialDocsBlogGitHubFacebookLinkedinSearchJavaScript[JS][email protected]🔖Object@MDN>Webtechnologyfordevelopers>JavaScript🔖JavaScriptobjectbasics@MDN>LearnWebDevelopment>JavaScript🔖重新認識JavaScript:Day22深入理解JavaScript物件屬性@iThome鐵人賽使用物件://同時對objectdestructuring的變數「重新命名」和給予「預設值」const{message:msg='Somethingwentwrong'}={message:'Hello'};Object.keys(obj);//列出物件的所有鍵(需為可列舉的屬性)Object.values(obj);//取得物件的所有值Object.entries(obj);//把物件的key和value攤成陣列letcopyObj={...obj,weight:67};//複製物件,並以更新值取代obj.hasOwnProperty(propertyName);//檢驗是否為該物件原本就具有的屬性(繼承而來的不會顯示)Object.is(value1,value2);//比較value1和value2是否為相同的valuedeleteobject.property;//刪除物件中的屬性定義與建立物件:constnewObject=Object.create(obj);//newObject會繼承obj的物件//定義物件property的attributes/flag(descriptors)Object.defineProperty(obj,propertyName,{//attributes/flags(descriptors)value:undefined,writable:false,enumerable:false,configurable:false,});Object.defineProperties(obj,{propertyName1:{/*descriptors*/},propertyName2:{/*descriptors*/}})//檢視物件中某個property的attributes/flag(descriptors)constdescriptor=Object.getOwnPropertyDescriptor(obj,propertyName);constdescriptors=Object.getOwnPropertyDescriptors(obj);//連同descriptors一併複製出新的物件constclonedObj={};Object.defineProperties(clonedObj,Object.getOwnPropertyDescriptors(obj));觀念​在JavaScript中,差不多所有事物都是物件,除了null及undefined以外,其它所有原始類型都可以看成是物件。

Snippets​依據條件指定物件屬性(conditionallyassignobjectkey)​keywords:dynamicobjectkey,dynamicobjectproperty​//Willresultin{foo:'foo',bar:'bar'}constitem={foo:'foo',...(true&&{bar:'bar'}),...(false&&{falsy:'falsy'}),};console.log(item);Assign,andsetjsobjectpropertyconditionally@StackOverflowInJavascript,howtoconditionallyaddamembertoanobject?@StackOverflow還有一些類似的用法,像是:letplanA:object|undefined;planA={foo:'foo',};letplanB:object|undefined;planB={bar:'bar',};constplan={...(planA&&{planA}),//planA存在的話,就把{planA}放進去物件//...(planA?{planA}:{}),//相同結果的寫法...(planB||undefined),//planB存在的話就直接把它解開來...(planB||{}),//相同結果的寫法};//plan//{//"planA":{//"foo":"foo"//},//"bar":"bar"//}將物件扁平化​keywords:arraytoobject,objectflatten​letarr=[{a:10},{b:20},{c:30}];letobj=arr.reduce((acc,current,index,array)=>{returnObject.assign(acc,current);},{});console.log(obj);//{a:10,b:20,c:30}console.log({...arr});//{0:{a:10},1:{b:20},2:{c:30}}簡潔的列出物件的屬性​keywords:objecttoarray,Object.entries()​constcars={BMW:3,Tesla:2,Toyota:1,};Object.entries(cars);//[['BMW',3],['Tesla',2],['Toyota',1]]for(let[key,value]ofObject.entries(cars)){console.log(key,value);}移除物件屬性(immutable,purefunction)​constobj={foo:'bar',stack:'overflow',};//deletefooconst{foo,...newObject}=obj;console.log(newObject);//{stack:'overflow'}//deletefoothroughdynamickeyconstkeyToDelete='stack';const{[keyToDelete]:value,...newObject}=obj;console.log(newObject);//{foo:'bar'}資料來源:Removeapropertyinanobjectimmutably@StackOverflow物件的擴展(objectliteralextension)​基本使用​在ES6中,我們可以不對物件屬性賦值,它會自動以屬性名稱當作屬性值:letobj={name,country};//等同於obj={name:name,country:country}當我們寫:letname='Aaron';letcountry='Taiwan';letobj={name,country};//等同於obj={name:name,country:country}console.log(obj);//[Object]{name:"Aaron",country:"Taiwan"}所以賦值的情況如下:因此,我們可以想像,如果我們寫成這樣:letwebsite='pjchender';letobj={abc:website};console.log(obj);//[object]{abc:"pjchender}要留意的是如果有直接在objectliteral中賦值,會覆蓋掉在更上面時所做的宣告:letname='PJCHENder';//會被覆蓋letcountry='Taiwan';letobj_es6={name:'Aaron',country,};console.log(obj_es6);//[Object]{name:"Aaron",country:"Taiwan"}簡寫物件中的函式/方法​letobj={location(){//等同於location:function(){...}//...},};obj.location();允許將表達式作為屬性名稱(動態賦予屬性名稱)​keywords:dynamicobjectpropertykey​允許將表達式作為屬性的名稱,只需要使用[]就可以了:letwebsiteName='pjchender';leta=2;letb=3;letobj_es={[websiteName]:'welcome',[a+b]:'sumNumber',};console.log(obj_es);//[Object]{5:"sumNumber",pjchender:"welcome"}letdata=[{name:'Aaron',height:'171',},{name:'Lynn',height:'160',},];letobj=data.map((item)=>{return{[item.name]:item.height};});console.log(obj);//[{Aaron:'171'},{Lynn:'160'}]物件的解構賦值(objectdestructuring)​物件的解構賦值@阮一峰概念​陣列的解構賦值強調的順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。

在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象:let{website,country}={website:'pjchender',country:'Taiwan'};let{website:website,country:country}={website:'pjchender',country:'Taiwan',};//等同於console.log(website);//pjchenderconsole.log(country);//Taiwan修改變數名稱​//變數物件後面的值才是真正被建立和賦值的對象let{website:wb,country:ct}={website:'pjchender',country:'Taiwan'};console.log(website,country);//websiteinnotdefinedconsole.log(wb,ct);//"pjchender","Taiwan"解構undefined會是空物件​要留意的是,如果被解構的內容是undefined的話,那麼等於什麼都沒做,解構後的變數就會是空物件{}:constfoo={...undefined};console.log(foo);//{}這點對於在使用TS型別推斷時有一個重要的影響,當物件的key有可能是undefined,且使用了解構賦值,那麼即使一開始宣告的屬性是required的(T),但推斷出來的物件屬性值還是會變成undefined(bar的型別):typeT={a:number;b:string};constobj:{someKey?:T}={};constfoo=obj['someKey'];//T|undefinedconstbar={...obj['someKey']};//{//a?:number|undefined;//b?:string|undefined;//}為變數建立預設值​let{name,country='Taiwan'}={};name;//undefinedcountry;//'Taiwan'var{x=3}={};x;//3var{x,y=5}={x:1};x;//1y;//5var{x:y=3}={};y;//3var{x:y=3}={x:5};y;//5同時對變數重新命名和給予預設值​var{message:msg='Somethingwentwrong'}={};msg;//"Somethingwentwrong"物件解構賦值的用途和使用時機​快速取出JSON物件的屬性​letdata_JSON={id:74,website:'pjchender',country:'Taiwan',detail:{add:'Tainan',phone:'0933333333',},};let{id,website,country,detail}=data_JSON;console.log(id,website,country,detail);在函式時取得想要的物件屬序​//@airbnb5.1letuser={firstName:'Aaron',lastName:'Chen',detail:{height:171,weight:68,},};//goodfunctiongetFullName(user){const{firstName,lastName}=user;return`${firstName}${lastName}`;}//bestfunctiongetFullName({firstName,lastName}){return`${firstName}${lastName}`;}在callbackfunction中取得所需的屬性​letarr=[{name:'Aaron',age:28},{name:'John',age:32},{name:'Albert',age:26},];//直接取得物件的屬性arr.forEach(({name,age})=>console.log(`${name}is${age}year'sold`));//只取得想要使用的屬性arr.map(({name})=>name);//['Aaron','John','Albert']物件常用方法​避免改變原本的物件(immutable)​使用展開語法(spreadsyntax)​//使用避免改變原本物件的方法letcar={type:'vehicle',wheels:4};//使用展開語法(spreadsyntax)letfordGtWithSpread={make:'Ford',//添加新的屬性...car,wheels:3,//覆蓋原有的屬性};console.log(fordGt);//{make:"Ford",type:"vehicle",wheels:3}//使用Object.assignletfordGtWithObjectAssign=Object.assign({},car,{make:'Ford',wheels:3,});console.log(fordGtWithObjectAssign);//{type:"vehicle",wheels:3,make:"Ford"}檢驗物件使否有該屬性​keywords:Object.getOwnPropertyNames(),.hasOwnProperty(),Object.keys()​Object.getOwnPropertyNames():列出該物件所就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。

.hasOwnProperty():檢驗是否為該物件原本就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。

Object.keys():以陣列的方式列出該物件可列舉的屬性鍵//定義一個functionconstructorfunctionPerson(firstName,lastName){this.firstName=firstName;this.lastName=lastName;this.say_hello=function(){return`Hello,${firstName}${lastName}`;};}//該functionconstructor繼承一個getFullName方法Person.prototype.getFullName=function(){return`Yournameis${this.firstName}${this.lastName}`;};//利用functionconstructor建立一個aaron物件aaron=newPerson('Aaron','Chen');//為aaron物件定義一個notenumerable的屬性'height'Object.defineProperty(aaron,'height',{value:171,enumerable:false,});//obj.hasOwnProperty()只要是非繼承而來的屬性鍵都回傳true,不論是否為enumerableaaron.hasOwnProperty('firstName');//true,enumerableaaron.hasOwnProperty('height');//true,non-enumerableaaron.hasOwnProperty('getFullName');//false,inheritance//Object.getOwnPropertyNames()會列出所有非繼承而來的屬性鍵,不論是否為enumerableObject.getOwnPropertyNames(aaron);//['firstName','lastName','say_hello','height']//Object.keys()列出所有enumerable和non-inheritance的屬性鍵Object.keys(aaron);//['firstName','lastName','say_hello']複製物件​keywords:copyobject​在JavaScript中用了複製物件的方式有很多,最常見的是使用objectspreadoperators,如果在標的物件裡的屬性名稱(key)和來源物件的屬性名稱相同,將會被覆寫。

若來源物件之間又有相同的屬性名稱,則後者會將前者覆寫。

/***Object.assign(target,...sources)*@airbnb3.8**/varobj1={foo:'bar',x:42};varobj2={foo:'baz',y:13};//使用ObjectdestructingvarclonedObj={...obj1};//Object{foo:"bar",x:42}varmergedObj={...obj1,...obj2};//Object{foo:"baz",x:42,y:13}//bad:使用Object.assign()會觸發setters,而spreadsyntax不會objCopy=Object.assign({},obj,{height:173});console.log(objCopy);//{name:'Aaron',height:173}一般來說使用Objectdestructuring就可以複製物件的key-valuepair,但這麼做並不會複製這些property的descriptors,如果想要完整的連descriptors都一併複製,則可以搭配使用Object.getOwnPropertyDescriptors(obj)和Object.defineProperties():letcloneUser={};Object.defineProperties(cloneUser,Object.getOwnPropertyDescriptors(user));Object.create(obj):根據指定的prototype來建立新的物件​透過Object.create(obj)的方式,我們可以省去先建立建構函式(constructors)才建立物件的麻煩:letperson={firstName:'Aaron',lastName:'Chen',greet:function(){console.log(`${this.firstName}${this.lastName}`);},};letjohn=Object.create(person);//John會繼承person這個物件延伸補充:使用Object.create(null)和{}建立物件的差別?​IscreatingJSobjectwithObject.create(null)thesameas{}?@StackOverflow但我們使用{}來建立空物件時,實際上這個物件會繼承有Object所提供的方法;如果是使用Object.create(null)所建立出來的物件,才會是不包含任何內建方法的「純物件」。

也就是說,{}等同於Object.create(Object.prototype)。

繼承相關​keywords:Object.getPrototypeOf(obj),obj.constructor​Object.getPrototypeOf(obj);//取得某一物件的__proto__obj.constructor;//取得某物件的建構式名稱getter/setter​在使用objectliteralnotation在定義getters和setters時,你只需要在方法前使用get或set即可,其中get不需要填入任何參數;set則可以填入取得的新值作為參數:letobj={name:'Aaron',interest:[],getname(){return'Thenamepropertyisforbiddentoget';},setsetInterest(newVal){this.interest.push(newVal);},};obj.name='Aaron';obj.setInterest='Programming';//setterconsole.log(obj);/*{name:[Getter],interest:['Programming'],setInterest:[Setter]}*/console.log(obj.name);//"Thenamepropertyisforbiddentoget"console.log(obj.interest);//['Programming']要直接在物件中使用get和set或者是透過Object.defineProperty()來定義getter和setter取決於寫程式的風格和當前的任務。

如果你在定義prototype時使用的是objectliteralnotation(objectinitializer),那麼可能會像這裡一樣,直接在物件中使用set和get;但如果你在定義物件的當下沒有設定,或者想要稍後設定,那麼可以使用Object.defineProperty()。

刪除物件屬性delete​obj={firstName:'Aaron',lastName:'Chen',};deleteobj.firstName;console.log('firstName'inobj);//yields"false"ObjectDescriptor(attributes/flags)​物件除了最基本key-value的使用外,還有其他特別的attributes可以使用,這些attributes也被稱作"flag":writable:如果是true則可以被修改,否則會是read-onlyenumerable:如果是true則可以在跑回圈時被列出configurable:如果是true則物件中的property可以被刪除,且上述的attributes也可以被修改預設來說,直接建立的物件這些attributes的值都會是true。

透過Object.getOwnPropertyDescriptor(obj,propertyName)可以檢視某物件的這些properties屬性://https://javascript.info/property-descriptorsconstuser={name:'John',};constdescriptor=Object.getOwnPropertyDescriptor(user,'name');console.log(descriptor);//{value:'John',writable:true,enumerable:true,configurable:true}property和attribute的差異property和attribute雖然中文都稱作「屬性」,但一般來說,"property"指的是物件的「key」;而上述的"writable","enumerable","configurable"則稱作是"attribute"。

如果想要修改這些attributes則可以使用Object.defineProperty(obj,propertyName,descriptor)來達到,在使用defineProperty時:如果該property已經存在物件中,則會直接更新該property的flag/attribute如果該property尚不存在物件中,則該property會被建立,且沒有被指定的flag則預設都會是falseletuser={};Object.defineProperty(user,'name',{value:'Aaron',enumerable:true});constdescriptor=Object.getOwnPropertyDescriptor(user,'name');/*{value:'Aaron',writable:false,enumerable:true,configurable:false}*/writable​如果把某物件property的attribute(flag)設為writable:false,則給property的value將無法被修改。

tip對non-writable的物件屬性進行操作時,只有在strictmode才會拋出TypeError,否則會「沒有任何事情發生」,但物件的屬性值並不會改變。

enumerable​如果把某物件property的attribute(flag)設為enumerable:false,則在這個property在疊代時將不會被列出,不論是使用for...in、Object.keys()或Object.values()都不會看到該property。

configurable​如果把某物件property的attribute(flag)設為configurable:false,則:該物件的property將無法被刪除該property的attribute也不能被改變。

然而,有一個例外是,writable依然可以從true變為false,但反過來則不可以。

許多JavaScript的內建物件都是屬於configurable:false,例如,Math.PI。

將configurable設成false後就是一條不歸路,一旦這個property的configurable變成false之後,就沒辦法在做任何調整,即使再使用Object.defineProperty()也無法改變。

//configurable:false有一個例外是writable可以從true設為false,但反過來則不行letuser={name:'Aaron',};Object.defineProperty(user,'name',{writable:true,configurable:false})//✅將writable從true改為false是可以的Object.defineProperty(user,'name',{writable:false,})//❌但若要把writable從false在改回true則不被允許//TypeError:Cannotredefineproperty:nameObject.defineProperty(user,'name',{writable:true,})Object.getOwnPropertyDescriptors(obj,prop)​透過Object.getOwnPropertyDescriptors可以取得一個物件中的所有descriptors,包含getter和setter在內。

[email protected](obj,prop,descriptor)​keywords:configurable,enumerable,writable,value,set,get​Object.defineProperty()@MDN-WebtechnologyfordevelopersObject.defineProperty(obj,prop,descriptor);Object.defineProperties(obj,{prop1:descriptor1,prop2:descriptor2//...});物件中的屬性描述器(Propertydescriptors)主要分成兩種-資料描述器(datadescriptors)和提取描述器(accessordescriptors)。

資料描述器(datadescriptors)是有值的屬性,這個屬性可能可以被修改(writable);提取描述器(accessordescriptors)則是透過getter-setter的函式來描述該屬性。

描述器只能是datadescriptors或accessordescriptors其中一種,不能同時是兩種。

不論是資料描述器或提取描述器都是物件,並且都包含相同的鍵:configurable:objectdescriptor的設定能否被改變或刪除。

預設false。

enumerable:該描述器是否會在被列舉的(for...in,Object.keys())過程中顯示。

預設false。

datadescriptor包含以下選擇性(optional)的鍵:value:屬性的值。

預設undefined。

writable:能否透過=改變該屬性值。

預設false。

accessordescriptor包含以下選擇性(optional)的鍵:get:可以作為該屬性的getter,預設是undefined。

set:可以作為該屬性的setter,預設是undefined。

為了要確保這些預設值都有被保留,你可以凍結(freeze)Object.prototype、明確的定義所有的項目、或者使用Object.create(null)。

//datadescriptorwithdefaultvalueObject.defineProperty(obj,'key',{enumerable:false,configurable:false,writable:false,value:undefined,});建立物件屬性(Creatingaproperty)​當所定義的屬性不存在物件中時,Object.defineProperty()會建立一個新的property:資料描述器(DataDescriptor)​/***DataDescriptor*一般以ObjectLiteral定義物件時,所有的attribute/flag預設都是true**/varo={};//CreatesanewobjectObject.defineProperty(o,'a',{value:37,writable:true,//設為false則無法改變該屬性值enumerable:true,//如果設為false,則o=>{}configurable:true,});console.log(o);//{a:37}console.log(o.a);//37提取描述器(AccessorDescriptor)​/***AccessorDescriptor**/leto={};varbValue=38;Object.defineProperty(o,'b',{get(){returnbValue;},set(newValue){bValue=newValue;},enumerable:true,configurable:true,});console.log(o.b);//38o.b=18;console.log(o.b);//18改變物件屬性(Modifyingaproperty)​當物件屬性已經存在時,Object.defineProperty()會根據descriptor中的value和物件當時的configuration來試著改變物件屬性。

如果舊的描述器中的configurable為false,則稱作non-configurable,此時沒有屬性可以被更改,也沒辦法將描述器在data和accessor中轉換。

non-configurable:當物件的configurable被設成false時,稱作"non-configurable",此時沒辦法修改該物件的descriptor。

但有一個例外時,non-configurable的property依然可以將writable從true改為false(反過來則不可🙅‍♂️)。

和descriptor其他相關的methods​Object.preventExtensions(obj)Object.isExtensible(obj)obj.propertyIsEnumerable(prop)Object.getOwnPropertyDescriptor(obj,prop)Object.prototype.propertyIsEnumerable(prop)Object.preventExtensions()//用來避免物件被新增新的屬性Object.isExtensible()//檢驗物件可否擴充(新增屬性).propertyIsEnumerable()//檢驗該物件的某key能否在疊代時顯示Object.getOwnPropertyDescriptor(,)//取得某物件屬性的descriptorObject.prototype.propertyIsEnumerable()//檢驗該物件的屬性鍵是否為enumerableExtensible:當一個物件不是extensible的時候,表示沒辦法為該物件新增屬性。

封裝整個物件的方法​keywords:Object.preventExtensions(obj),Object.seal(obj),Object.freeze(obj)前面提到可以透過Object.defineProperties()來修改物件中個別property的descriptors。

如果想要針對的是一整個物件的話,則可以透過下述方法:方法extensiblewritableenumerableconfigurableObject.preventExtensions(obj)falsetruetruetrueObject.seal(obj)falsetruetruefalseObject.freeze(obj)falsefalsetruefalseextensible指的是能不能對該物件添加新的property。

preventextensions:不可以添加新的property​varo={name:'Foobar',};/***使用Object.preventExtensions(obj)時:*不可新增property(non-extensible)*writable:true,enumerable:true,configurable:true**/Object.isExtensible(o);//trueObject.preventExtensions(o);Object.isExtensible(o);//falseObject.isSealed(o);//falseObject.isFrozen(o);//falseObject.getOwnPropertyDescriptor(o,'name');seal:non-extensible+non-configurable​/***使用Object.seal(obj)時:*不可新增屬性(non-extensible)*writable:true,enumerable:true,configurable:false**/Object.isExtensible(o);//trueObject.seal(o);Object.isExtensible(o);//falseObject.isSealed(o);//trueObject.isFrozen(o);//falseObject.getOwnPropertyDescriptor(o,'name');freeze:non-extensible+non-configurable+non-writable​/***使用Object.freeze(obj)時:*不可新增屬性(non-extensible)*writable:false,enumerable:true,configurable:false**/Object.isExtensible(o);//trueObject.freeze(o);Object.isExtensible(o);//falseObject.isSealed(o);//trueObject.isFrozen(o);//trueObject.getOwnPropertyDescriptor(o,'name');物件屬性的順序​物件中屬性的順序取決於屬性的類型和它的值,一般來說數值會在最前面、接著是字串、最後是Symbol:varobj={2:'integer:2',foo:'string:foo','01':'string:01',1:'integer:1',[Symbol('first')]:'symbol:first',};Object.keys(obj);//["1","2","foo","01"];Reflect.ownKeys(obj);//["1","2","foo","01",Symbol(first)]CodeStyle​使用ES6提供的縮寫​@airbnb3.5//goodconstatom={value:1,addValue(value){returnatom.value+value;},};//good,使用縮寫的放在最上面constobj={lukeSkyWalker,lucySkyWalker,episodeOne:1,twoJediWalkIntoACantina:2,episodeThree:3,mayTheFourth:4,};不要直接呼叫Object.prototype的方法​@airbnb3.7//badconsole.log(object.hasOwnProperty(key));//goodconsole.log(Object.prototype.hasOwnProperty.call(object,key));//bestconsthas=Object.prototype.hasOwnProperty;//cachethelookuponce,inmodulescope./*or*/importhasfrom'has';//...console.log(has.call(object,key));參考​JavaScriptFunction參考文件@MDN物件的使用@MDN>JavaScript指南物件的所有方法@MDNPrevious[JS]numberandmathNext[JS]OO觀念Snippets依據條件指定物件屬性(conditionallyassignobjectkey)將物件扁平化簡潔的列出物件的屬性移除物件屬性(immutable,purefunction)物件的擴展(objectliteralextension)基本使用簡寫物件中的函式/方法允許將表達式作為屬性名稱(動態賦予屬性名稱)物件的解構賦值(objectdestructuring)概念修改變數名稱為變數建立預設值同時對變數重新命名和給予預設值物件解構賦值的用途和使用時機物件常用方法避免改變原本的物件(immutable)檢驗物件使否有該屬性複製物件Object.create(obj):根據指定的prototype來建立新的物件繼承相關getter/setter刪除物件屬性deleteObjectDescriptor(attributes/flags)Object.getOwnPropertyDescriptors(obj,prop)Object.defineProperty(obj,prop,descriptor)封裝整個物件的方法物件屬性的順序CodeStyle使用ES6提供的縮寫不要直接呼叫Object.prototype的方法參考



請為這篇文章評分?