首頁 > 人文
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函式的語句