首頁 > 人文

ES中物件的擴充套件(程式碼解析)

由 李不要熬夜 發表于 人文2021-12-08

簡介getOwnPropertyDescriptor(obj, ‘foo’)*{value: ‘foo’,writable: true,enume

不可列舉屬性怎麼獲得

屬性的可列舉性

可列舉性

物件的每個屬性都有一個描述物件(Desciprtor),用來控制該屬性的行為。Object。getOwnPropertyDescriptor 方法可以獲取該屬性的描述物件

let obj = {foo: ‘foo’} Object。getOwnPropertyDescriptor(obj, ‘foo’) /* { value: ‘foo’, writable: true, enumerable: true, configurable: true } */

描述物件的enumerable屬性,稱為’可列舉性’, 如果該屬性為false,表示某些操作會忽略當前屬性

目前,有四個操作會忽略enumerable為false的屬性

for in 只遍歷物件自身和繼承的可列舉屬性

object。keys 只返回可遍歷的屬性名

Json。stringify 只序列化物件自身的可列舉的屬性

Object。assign 忽略enumerable為false的屬性,只複製物件自身的可列舉屬性

引入可列舉性這個概念的最初目的,就是讓某些屬性可以避免被for…in,否則內部的方法,屬性都會被遍歷到。比如物件的tostring,陣列的length

Object。getOwnPropertyDescriptor(Object。prototype, ‘toString’)。enumable // false Object。getOwnPropertyDescriptor([], ‘length’)。enumable // false

上面程式碼物件的toString 和陣列的length 屬性的enumable 都是false,所以不會被for…in遍歷到

ES6規定所有class的繼承屬性都是不可列舉的

Object。getOwnPropertyDescriptor(class {foo() {}}。prototype, ‘foo’)。enumerable // false

由於for…in總是引入繼承的屬性,所以儘量使用Object。keys

屬性的遍歷

ES6一共有五種方法,可以遍歷物件的屬性

(1)for…in

for…in遍歷物件自身和繼承的可列舉的屬性(不包含Symbol)

(2)Object.keys(obj)

Object。keys返回一個數組,包含自身所有(不含繼承)可列舉的屬性(不含Symbol)鍵名

(3)Object.getOwnPropertyNames(obj)

Object。getOwnPropertyNames返回一個數組,包含物件自身所有屬性(不含Symbol,含有不可列舉)屬性的鍵名

(4)Object.getOwnPropertySymbols(obj)

Object。getOwnPropertySymbols 返回一個數組,包含自身所有包含Symbol屬性的鍵名

(5)Reflect.ownKeys(obj)

Reflect。ownKeys返回一個數組,包含物件自身所有屬性(不含繼承),不管鍵名是否是Symbol,不管是否可列舉

以上的5種方法遍歷物件的鍵名,都遵循同樣的屬性遍歷的次序規則

首先遍歷所有數值鍵,按照數值升序排列

其次遍歷所有字串鍵,按照加入時間升序排列

最後遍歷所有Symbol鍵,按照加入時間升序排列

let obj = { [Symbol()]: 0, b: 0, 3: 9, 10: 20, a: 9 } Reflect。ownKeys(obj) // [3, 10, b, a, Symbol()]

Reflect。ownKeys方法會返回一個數組,這個陣列的返回次序是這樣的,

首先是數值3和10,其次是字串b和a,最後是symbol

super關鍵字

this總是指向函式所在的當前物件,ES6新增了super關鍵字,它指代的是當前物件的原型物件

this ——> 當前物件

super ——> 當前物件的原型物件

let person = { name: ‘person’ } let son = { name: ‘sun’, printPerson () { return super。name } } Object。setPrototypeOf(son, person) son。printPerson() // ‘person’

上面透過設定son的原型,呼叫son方法來找到原型的屬性

super表示原型物件時,只能用在物件的方法之中,用在其他地方會報錯

let obj = { foo: super。foo } // 用在了屬性上 let obj1 = { foo: () => super。foo } // 用在了方法 let obj2 = { foo: function () { return super。foo } }

上面三種都會報錯,因為對於js引擎而言,根本沒有用到super。

第一種屬於用在屬性中

第二種和第三種屬於用在函式中,但又賦值給了foo屬性

現在只有簡寫的物件方法才能讓js引擎確認,定義的是物件的方法

js引擎內部,super。foo等同於

Object。getPrototypeOf(this)。foo 屬性 或 Object。getPrototypeOf(this)。foo。call(this) 方法。

let father = { name: ‘person’, sayHello () { return this。name } } let son = { name: ‘son’, sayHi () { return super。sayHello(); } } Object。setPrototypeOf(son, father); son。sayHi(); // ‘son’

上面程式碼分為三步

第一步呼叫son。sayHi 函式返回了原型的sayHello方法

第二步father自身呼叫sayHello方法,返回了this。name

第三部this。name 此時的this,因為在son的環境執行的所以,this指向son,所以列印結果為’son’

如果改為father。sayHello() 就不一樣了

物件的擴充套件運算子

陣列中的擴充套件運算子(…)以及得心應手了,物件的寫法與之功能基本類似

解構賦值

物件解構用於將一個物件的值全部取出來(可遍歷的),並且沒有被讀取的屬性,賦值到另一個物件身上,所有他們的鍵和值,最後形成一個新物件

let {x, y, 。。。z} = {x: 1, y: 2, z: 3, u: 10, n: 20} x // 1 y // 2 z // {z: 3, u: 10, n: 20}

上面程式碼只有z是解構成功的物件,x和y屬於被讀取過後的值了,z會把x y沒有讀取到的鍵和值複製過來,返回一個新陣列

解構賦值等號右邊必須是一個物件,如果不是就會報錯

let {。。。y} = undefined // error let {。。。n} = null // error

解構賦值必須是最後一個引數,否則會報錯

let {。。。x, y} = obj; // error let {x, 。。。y, z} = obj; // error

解構賦值是淺複製,即如果複製的值是一個數組,物件,函式,那麼複製的實際是引用,而不是副本

let obj = { a: {b: 1} } let {。。。newObj} = obj newObj。a。b = 2; obj。a。b // 2

上面程式碼複製的是一個物件,由於解構只是淺複製,所以指向了同一個指標地址,導致新地址的資料改變,原地址指向的資料也要發生改變,因為他們的指向同一個房間。

解構賦值無法拿到原型的屬性

let a1 = {bar: ‘bar’} let a2 = {foo: ‘foo’} Object。setPrototypeOf(a1, a2) let {。。。a3} = a2 a3 // {foo: ‘foo’} a3。bar // undefined

上面程式碼a2 繼承a1,a3又複製了a2 ,但是解構賦值無法拿到原型屬性,導致a3沒有獲取到a1這個祖先的屬性

// 建立一個obj的原型物件 let obj = Object。create({x: 1, y: 2}) // 自身新增一個z屬性 obj。z = 3 let {x, 。。。newObj} = obj // x是單純的解構賦值,所以可以讀取繼承屬性。 // y和z是擴充套件運算子解構賦值,只能讀取物件自身的屬性 let {y, z} = newObj // y是繼承屬性,所以newObj中獲取不到。 x // 1 y // undefined z // 3

可能有人覺得可以省事,會這麼寫

let {x, 。。。{y, z}} = obj // error

ES6規定,變數宣告語句中,如果使用解構賦值,擴充套件運算子後面必須是一個變數名,而不能是一個解構賦值表示式。

解構賦值的一個用處,是擴充套件某個函式的引數,傳遞給另一個函式或者引入其他操作

function test (a, b) { return a + b } function bar (x, y, 。。。anther) { // 使用x y 進行內部操作 // 把擴充套件運算子匯出出去,提供給另一個函式,或者做別的操作 console。log(x * y); return test(anther); } bar(1, 3, 9, 9)

擴充套件運算子

物件的擴充套件運算子,用於取出引數物件所有可遍歷屬性,複製到當前物件之中

let z = {x: 1, y: 2} let n = {。。。z} n // {x: 1, y: 2}

由於陣列是特殊的物件,所以物件擴充套件運算子也可以用於陣列

let foo = {。。。[‘x’, ‘y’, ‘z’]} foo // {0: ‘x’, 1: ‘y’, 2: ‘z’}

如果擴充套件運算子後面是一個空物件,則沒有任何效果

let obj = {。。。{}, a: 1} obj // {a: 1}

如果不是個物件,會把他轉換為物件

// {。。。Object(1)} 等同於 {。。。1} // {}

上面會呼叫包裝類,轉換為Number(1) 但該物件自身本就沒有任何屬性,所以返回空

還有一些型別

{。。。true} // {} {。。。undefined} // {} {。。。null} // {} {。。。‘es6’} // {0: ‘e’, 1: ‘s’, 2: ‘6’}

前三個都會返回空物件,最後一個字串,由於字串會轉換為類陣列,所以返回的就是一組帶索引的物件

物件的擴充套件運算子等同於使用Object。assign() 方法

let aClone = {。。。a}; // 等同於 let aClone = Object。assign({}, a)

上面只是複製物件例項屬性,想要複製原型屬性,需要這樣寫

// fn1 let clone1 = { // 拿到obj的原型,並賦給自己的proto, 自己的原型指向了obj的原型 __proto__ : Object。getPrototypeOf(obj), // obj的例項屬性賦給自己原型,複製obj的例項屬性和原型屬性 。。。obj } // fn2 let clone2 = Object。assign( // 建立一個物件,該物件的原型是obj原型物件 Object。create(Object。getPrototypeOf(obj)), // 把obj放到上面這個物件裡 obj ) // fn3 let clone3 = Object。create( // 建立參照物件,該物件為obj的原型物件 Object。getPrototypeOf(obj), // 把obj的所有可列舉屬性傳遞給參照物件 Object。getOwnPropertyDescriptors(obj) )

擴充套件運算子,可以合併物件

let obj1 = {。。。a, 。。。b} let obj2 = Object。assign({}, a, b)

如果擴充套件運算子後面還有使用者自定義的屬性,那麼擴充套件運算子內部的同名屬性會被覆蓋掉

let obj = {。。。a, x: 1, y: 2} // 等同於 let obj1 = {。。。a, 。。。{x: 1, y: 2}} // 等同於 let x = 1, y = 2, obj2 = {。。。a, x, y} // 等同於 let obj3 = Object。assign({}, a, {x: 1, y: 2})

上面程式碼中,a物件中的x, y屬性都會被 a後面的 x y都會在複製到新物件時覆蓋掉

這樣一來更改部分現有屬性,很方便

let newVersion = { 。。。oldVersion, name: ‘new Version’ }

上面的舊版本 name會被替換掉

如果自定義屬性放在擴充套件運算子前,就會變成設定新物件的預設屬性值

let obj = {x: 1, y: 2, 。。。a} == let obj1 = Object。assign({}, {x: 1, y: 2}, a == let obj2 = Object。assign({x: 1, y: 2}, a)

物件擴充套件運算子後面也可以跟表示式

let obj = { 。。。(x > 1 : {name: ‘wei’} ? {}), bar: ‘foo’ }

如果擴充套件運算子的引數物件之中,有取值函式get,它會自動執行的

let obj = { get foo () { console。log(1) } } let newObj = {。。。obj} // 1

上面程式碼,在擴充套件結束後就會執行get函式的語句

Tags:letobj屬性object物件