概念
ecmascript 5 中定義了一個名叫“屬性描述符”的對象,用于描述了的各種特征。屬性描述符對象有4個屬性:
configurable:可配置性,控制着其描述的屬性的修改,表示能否修改屬性的特性,能否把屬性修改為通路器屬性,或者能否通過delete删除屬性進而重新定義屬性。預設值為true。
enumerable:可枚舉性,表示能否通過for-in周遊得到屬性。預設值為true。
writable:可寫性,表示能否修改屬性的值。預設值為true。
value:資料屬性,表示屬性的值。預設值為undefined。
除了上面的屬性,還有兩個存取器屬性,分别是get和set,可以代替value和writable。
get:在讀取屬性時調用的函數。隻指定get則表示屬性為隻讀屬性。預設值為undefined。
set:在寫入屬性時調用的函數。隻指定set則表示屬性為隻寫屬性。預設值為undefined。
使用
“屬性描述符”對象隻能在object.defineproperty或object.defineproperties中使用。
api 用法
var hello = {}
object.defineproperty(hello, 'girl', {
configurable: false,
enumberable: false,
writable: true,
value: 'sexy'
})
// 存取器
object.defineproperty(hello, 'woman', {
get: function() {
return this.girl
},
set: function(val) {
this.girl = val
}
// 定義多個屬性
object.defineproperties(hello, {
boy: {
configurable: false,
enumberable: false,
writable: false,
value: 'handsome'
man: {
writable: true,
get: function() {
return this.boy
}
})
當用object.defineproperty或object.defineproperties操作(建立或者修改)那些不允許建立或修改的屬性時,會抛出類型錯誤異常。
// 此例子運作在前面的例子的基礎上
object.defineproperty(hello, 'boy', {
writable: true
}) // uncaught typeerror: cannot redefine property: boy
因為前面boy屬性已經被設定為不可配置,是以這裡修改writable會抛出類型錯誤異常。
通過object.getownpropertydescriptor或者object.getownpropertydescriptors可以得到屬性描述符。
規則
var rules = {
common: 'test'
}
如果屬性是不可配置的,則不能修改它的可配置性和可枚舉性。
object.defineproperty(rules, 'rule1', {
enumberable: false
// 修改configurable會抛出類型錯誤異常
configurable: true
}) // uncaught typeerror: cannot redefine property: rule1
// 修改enumberable不會抛出異常,但enmuberable沒有被修改
enumberable: true
object.getownpropertydescriptor(rules, 'rule1') // object {value: undefined, writable: false, enumerable: false, configurable: false}
如果存取器屬性是不可配置的,則不能修改get和set方法,也不能将它轉換為資料屬性。
object.defineproperty(rules, 'rule2', {
return this.common
this.common = val
// 修改get或者set方法會抛出類型錯誤異常
return this.common + 'rule2'
}) // uncaught typeerror: cannot redefine property: rule2
this.common = 'rule2'
// 将它轉換為資料屬性同樣會抛出類型錯誤異常
value: 'rule2'
}) // uncaught typeerror: cannot redefine property: rule2
如果資料屬性是不可配置的,則不能将它轉換為存取器屬性;同時,也不能将它的可寫性從false修改為true,但可以從true修改為false。
object.defineproperty(rules, 'rule3', {
writable: false,
value: 'rule3'
// 修改writable為true會抛出類型錯誤異常
object.defineproperty(rules, 'rule4', {
value: 'rule4'
// 可以修改writable為false
writable: false
object.getownpropertydescriptor(rules, 'rule4') // object {value: "rule4", writable: false, enumerable: false, configurable: false}
如果資料屬性是不可配置且不可寫的,則不能修改他的值;如果是可配置但不可寫,則可以修改他的值(實際上是先将它标記為可寫的,然後修改它的值,最後再将它标記回不可寫)。
其實這裡所說的修改值,是通過object.defineproperty或object.defineproperties方法修改。通過直接指派的方法在資料屬性不可配置的情況下是不能修改屬性值的。
object.defineproperty(rules, 'rule5', {
value: 'rule5'
// 修改屬性值會抛出類型錯誤異常
value: 'rule55'
}) // uncaught typeerror: cannot redefine property: rule5
rules.rule5 = 'rule55'
// 值沒有被修改,也不會抛出異常
rules.rule5 // 'rule5'
object.defineproperty(rules, 'rule6', {
configurable: true,
value: 'rule6'
// 修改屬性值
value: 'rule66'
rules.rule6 // 'rule66'
rules.rule6 = 'rule6'
// 值沒有被修改,也不會修改
rules.rule6 // 'rule6'
隻指定set不能讀,如果嘗試讀取該屬性值,傳回undefined。(紅寶書上說在嚴格模式下才抛出異常,但沒有)
object.defineproperty(rules, 'rule7', {
rules.rule7 = 'rule7' // uncaught typeerror: cannot redefine property: rule7
如果對象是不可擴充的,則可以編輯已有的自有屬性,但不能給它添加新屬性。
操作對象可擴充性的api有三個:object.preventextensions、object.seal、object.freeze。
使用object.preventextensions可以将對象轉換為不可擴充。
使用object.isextensions來判斷對象是否可擴充。
var ex = {}
object.defineproperty(ex, 'ex1', {
value: 'ex1'
object.isextensible(ex) // true
object.preventextensions(ex)
object.isextensible(ex) // false
// 可以修改已有的屬性
value: 'ex11'
object.getownpropertydescriptor(ex, 'ex1') // object {value: "ex11", writable: false, enumerable: false, configurable: true}
// 添加屬性會抛出類型錯誤異常
object.defineproperty(ex, 'ex2', {
value: 'ex2'
}) // uncaught typeerror: cannot define property:ex2, object is not extensible.
使用object.seal除了可以将對象轉換為不可擴充的,還可以将對象的所有自有屬性都轉換為不可配置的。即不能給對象添加新屬性,而且它已有的屬性也不能删除或者配置(這裡同樣會遵循前面的規則)。
使用object.issealed來判斷對象是否封閉(sealed)。
var se = {}
object.defineproperty(se, 'se1', {
value: 'se1'
object.issealed(se) // false
object.seal(se)
object.issealed(se) // true
// 修改已有的屬性會抛出類型錯誤異常
value: 'se11'
}) // uncaught typeerror: cannot redefine property: se1
object.defineproperty(se, 'se2', {
value: 'se2'
}) // uncaught typeerror: cannot define property:se2, object is not extensible.
使用object.freeze除了将對象轉換為不可擴充的和将其屬性轉換為不可配置的之外,還可以将自有屬性轉換為隻讀。(如果對象設定了set,存取器屬性将不會受影響,仍可以調用set方法,而且不會抛出異常,但如果set方法是改變該對象的屬性,則不能修改成功)
使用object.isfrozen來檢測對象是否當機(frozen)。
var fr = {}
object.defineproperty(fr, 'fr1', {
value: 'fr1'
object.isfrozen(fr) // false
object.freeze(fr)
object.isfrozen(fr) // true
value: 'fr11'
}) // uncaught typeerror: cannot redefine property: fr1
object.defineproperty(fr, 'fr2', {
value: 'fr2'
}) // uncaught typeerror: cannot define property:fr2, object is not extensible.
fr.fr1 = 'fr11'
// 不能修fr1屬性
fr.fr1 // 'fr1'
var set = {}
object.defineproperty(set, 'set1', {
value: 'set1'
object.defineproperty(set, 'set2', {
this.set1 = val
object.isfrozen(set) // false
object.freeze(set)
object.isfrozen(set) // true
set.set2 = 'set2'
set.set1 // 'set1'
結語
我對屬性描述符很不熟悉,主要是因為平時用得少。不過最近,開始學寫一些小的庫(雖然很挫),就感覺屬性描述符有使用的場景了。我暫時能想到的就是将庫對象的一些屬性設定為隻讀,以防止對象的一些屬性被使用者重寫覆寫了。還有一個用法是在知乎和學vue的時候知道的,就是通過getter和setter實作“監聽”對象屬性的資料更新(在這裡挖一個坑。後面學習一下這種方法,再寫一篇“監聽”對象屬性的資料更新的文章)。
最後,如果大家知道更多屬性描述符的使用後場景,希望大家能在評論區留下你們的高見。
作者:chenbright
來源:51cto