想到Object.defineProperty
,首先不得不提到對象,對象是JavaScript
的基礎,有一種常見的說法「JavaScript中萬物皆是對象」。javascript
這種說法其實並不那麼準確,根據 JavaScript 對語言類型的分類,就能夠得出JavaScript
並非萬物皆對象,此次就不對這個問題進行展開,感興趣能夠點擊JavaScript 萬物皆對象🤔。可是足以證實對象對於JavaScript
這門語言的重要性。java
Object.defineProperty
這個方法最經常使用的場景應該是在面試的時候,每當面試官問起Vue雙向綁定的原理,不少朋友可能破口而出就是這個方法,好了不開玩笑了,進入正題。面試
Object.defineProperty()
方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。數組該方法容許精確添加或修改對象的屬性。經過賦值操做添加的普通屬性是可枚舉的,可以在屬性枚舉期間呈現出來(
for...in
或Object.keys
方法),這些屬性的值能夠被改變,也能夠被刪除。這個方法容許修改默認的額外選項(或配置)。默認狀況下,使用Object.defineProperty()
添加的屬性值是不可修改的函數以上是MDN對於這個方法的描述post
在ES5以前,JavaScript
語言自己並無提供能夠直接檢測屬性特性的方法,可是從ES5開始,全部的屬性都具有了屬性描述符,有兩種主要形式:ui
描述符必須是這兩種形式之一,不能同時是二者,爲何後面會具體討論。this
writable
決定是否能夠修改屬性的值。spa
let myObject = {}
Object.defineProperty(myObject, 'a', {
value: 2,
writable: false, // 不可寫
configurable: true,
enumerable: true
})
myObject.a = 3
console.log(myObject.a) // 2
複製代碼
若是在嚴格模式下,會拋出TypeError
錯誤表示咱們沒法修改一個不可寫的屬性。雙向綁定
Configurable
決定對象屬性是否可配置,只要屬性是可配置的,就可使用defineProperty()
方法來修改屬性描述符:
let myObject = {
a: 2
}
myObject.a = 3
console.log(myObject.a) // 3
Object.defineProperty(myObject, 'a', {
value: 4,
writable: true,
configurable: false, // 不可配置
enumerable: true
})
console.log(myObject.a) // 4
myObject.a = 5
console.log(myObject.a) // 5
Object.defineProperty(myObject, 'a', {
value: 4,
writable: true,
configurable: true, // 修改成可配置
enumerable: true
}) // TypeError
複製代碼
不論是不是處於嚴格模式,嘗試修改一個不可配置的描述符都會拋出錯誤,如你所見,把configurable
修改爲false
是一個單向操做,沒法撤銷!
有一個小小的例外,即便configurable: false
,咱們仍是能夠把writable
的狀態由true
改成false
,可是沒法由false
改成true
。
除了沒法修改,configurable: false
還會禁止刪除這個屬性:
let myObject = {
a: 2
}
console.log(myObject.a) // 2
delete myObject.a
console.log(myObject.a) // undefined
Object.defineProperty(myObject, 'a', {
value: 2,
writable: true,
configurable: false, // 不可配置
enumerable: true
})
console.log(myObject.a) // 2
delete myObject.a
console.log(myObject.a) // 2
複製代碼
最後一個delete
語句失敗了,由於屬性不可配置。
從名字就能夠看出,這個描述符控制的是屬性是否出如今對象的屬性枚舉中,好比for...in
循環。
let myObject = {
a: 2
}
Object.defineProperty(myObject, 'a', {
value: 2,
enumerable: true // 可枚舉
})
Object.defineProperty(myObject, 'b', {
value: 2,
enumerable: false // 不可枚舉
})
console.log(myObject.b) // 2
console.log('b' in myObject) // true
console.log(myObject.hasOwnProperty('b')) // true
for (var k in myObject) {
console.log(k, myObject[k])
} // 'a' 2
複製代碼
能夠看到,myObject.b
確實存在而且有訪問值,可是卻不會for...in
循環中,儘管它確實存在於myObject
對象中。
in
和hasOwnProperty
的區別在因而否查找[[Prototype]]
鏈,in會沿着原型鏈往上查找
再看一個實例:
let myObject = {
a: 2
}
Object.defineProperty(myObject, 'a', {
value: 2,
enumerable: true // 可枚舉
})
Object.defineProperty(myObject, 'b', {
value: 2,
enumerable: false // 不可枚舉
})
console.log(myObject.propertyIsEnumerable('a')) // true
console.log(myObject.propertyIsEnumerable('b')) // false
console.log(Object.keys(myObject)) // ['a']
console.log(Object.getOwnPropertyNames(myObject)) // ['a', 'b']
複製代碼
propertyIsEnumerable()
會檢查給定的屬性名是否直接存在於對象中,而且知足enumerable: true
。
Object.keys()
會返回一個數組,包含全部的可枚舉屬性。Object.getOwnPropertyNames()
也會返回一個數組,包含全部屬性,不管它們是否可枚舉,這兩個方法都只會查找對象直接包含的屬性。
getter
和setter
能夠改寫默認操做,可是隻能應用在單個屬性上,沒法應用在整個對象,getter
和setter
都是隱藏函數,getter
會在獲取屬性值時調用,setter
會在設置屬性值時調用。好比Vue就會給全部的屬性添加上getter
和setter
函數。
當你給一個屬性定義getter
、setter
或者二者都有時,這個屬性會被定義爲「訪問描述符」(和「數據描述符」相對)。
對於訪問描述符來講,JavaScript
會忽略它們的value
和writable
特性,取而代之的是關心set
和get
(還有configurable
和enumerable
)特性。這就是爲何數據描述符和訪問描述符只會存在一個的緣由。
let myObject = {
get a() {
return 2
}
}
Object.defineProperty(myObject, 'b', {
get: function () {
return this.a * 2
}
})
console.log(myObject.a) // 2
console.log(myObject.b) // 4
複製代碼
我經過兩種方式建立了兩個不包含值的屬性,可是在訪問這兩個屬性的時候,它們都會自動調用一個隱藏的get
函數,函數的返回值會被看成屬性訪問的返回值。
let myObject = {
get a() {
return 2
}
}
myObject.a = 3
console.log(myObject.a) // 2
複製代碼
因爲定義了a
屬性的getter
,因此對a
的值進行設置時set
操做會忽略賦值操做。並且即使有合法的setter
,因爲咱們自定義的getter
只會返回2,因此set
操做時沒有意義的。
一般,getter
和setter
時成對出現的(只定義一個的話一般會產生意料以外的行爲):
let myObject = {
get a() {
return this._a_
}, // 給a定義一個getter
set a(val) {
this._a_ = val * 2
} // 給a定義一個setter
}
myObject.a = 2
console.log(myObject.a) // 4
複製代碼
當對目標對象的屬性設置了writable:false
,至關於你定義了一個空操做setter
,你的全部set
操做都會被忽略。
有時候你會但願屬性或者對象是不可改變的,在ES5中有不少方法能夠實現。很重要的一點,全部的方法建立的都是淺不可變性,也就是說,它們只會影響目標對象和它的直接屬性,若是目標對象引用了其餘對象,其餘對象的內容仍然是可變的。
結合writable:false
和configurable:false
就能夠建立一個真正的常量屬性,不可修改、從新定義或者刪除。
若是你但願獲得一個對象,它禁止添加新屬性而且保留已有屬性,可使用Object.preventExtensions()
:
let myObject = {
a: 2
}
Object.preventExtensions(myObject)
myObject.b = 3
console.log(myObject.b) // undefined
複製代碼
Object.seal()
會建立一個「密封」的對象,這個方法實際上會在一個現有對象上調用Object.preventExtensions()
並把全部現有屬性標記爲configurable:false
。
密封以後不只不能添加新屬性,也不能從新配置或者刪除任何現有屬性,可是能夠修改屬性的值。
Object.freeze()
會建立一個凍結對象,這個方法實際上會在一個現有對象上調用Object.seal()
並把全部「數據訪問」屬性標記爲writable:false
,這樣就不可修改它們的值。
這個方法是能夠應用在對象上的級別最高的不可變性,它會禁止對於對象自己及其任意直接屬性的修改。
以上的幾個方法都要慎用,頗有可能帶來意想不到的行爲。
文中若是有問題和遺漏,歡迎在評論區指出,若是本文能給幫助到你,請給個點贊
👍和關注
本文到此結束,886🚀🚀