ECMAScript 5 中定義了一個名叫「屬性描述符」的對象,用於描述了的各類特徵。屬性描述符對象有4個屬性:javascript
configurable
:可配置性,控制着其描述的屬性的修改,表示可否修改屬性的特性,可否把屬性修改成訪問器屬性,或者可否經過delete
刪除屬性從而從新定義屬性。默認值爲true
。vue
enumerable
:可枚舉性,表示可否經過for-in
遍歷獲得屬性。默認值爲true
。java
writable
:可寫性,表示可否修改屬性的值。默認值爲true
。函數
value
:數據屬性
,表示屬性的值。默認值爲undefined
。學習
除了上面的屬性,還有兩個存取器屬性
,分別是get
和set
,能夠代替value
和writable
。this
get
:在讀取屬性時調用的函數。只指定get
則表示屬性爲只讀屬性。默認值爲undefined
。code
set
:在寫入屬性時調用的函數。只指定set
則表示屬性爲只寫屬性。默認值爲undefined
。對象
「屬性描述符」對象只能在Object.defineProperty
或Object.defineProperties
中使用。ip
API 用法
Object.defineProperty:https://developer.mozilla.org...
Object.defineProperties: https://developer.mozilla.org...get
var hello = {} Object.defineProperty(hello, 'girl', { configurable: false, enumberable: false, writable: true, value: 'sexy' }) // 存取器 Object.defineProperty(hello, 'woman', { configurable: false, enumberable: false, get: function() { return this.girl }, set: function(val) { this.girl = val } }) // 定義多個屬性 Object.defineProperties(hello, { boy: { configurable: false, enumberable: false, writable: false, value: 'handsome' }, man: { configurable: false, enumberable: false, 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
能夠獲得屬性描述符。
API 用法
Object.getOwnPropertyDescriptor:https://developer.mozilla.org...
Object.getOwnPropertyDescriptors:https://developer.mozilla.org...
var rules = { common: 'test' }
若是屬性是不可配置的,則不能修改它的可配置性和可枚舉性。
Object.defineProperty(rules, 'rule1', { configurable: false, enumberable: false }) // 修改configurable會拋出類型錯誤異常 Object.defineProperty(rules, 'rule1', { configurable: true }) // Uncaught TypeError: Cannot redefine property: rule1 // 修改enumberable不會拋出異常,但enmuberable沒有被修改 Object.defineProperty(rules, 'rule1', { enumberable: true }) Object.getOwnPropertyDescriptor(rules, 'rule1') // Object {value: undefined, writable: false, enumerable: false, configurable: false}
若是存取器屬性是不可配置的,則不能修改
get
和set
方法,也不能將它轉換爲數據屬性。
Object.defineProperty(rules, 'rule2', { configurable: false, enumberable: false, get: function() { return this.common }, set: function(val) { this.common = val } }) // 修改get或者set方法會拋出類型錯誤異常 Object.defineProperty(rules, 'rule2', { get: function() { return this.common + 'rule2' } }) // Uncaught TypeError: Cannot redefine property: rule2 Object.defineProperty(rules, 'rule2', { set: function(val) { this.common = 'rule2' } }) // Uncaught TypeError: Cannot redefine property: rule2 // 將它轉換爲數據屬性一樣會拋出類型錯誤異常 Object.defineProperty(rules, 'rule2', { value: 'rule2' }) // Uncaught TypeError: Cannot redefine property: rule2
若是數據屬性是不可配置的,則不能將它轉換爲存取器屬性;同時,也不能將它的可寫性從
false
修改成true
,但能夠從true
修改成false
。
Object.defineProperty(rules, 'rule3', { configurable: false, writable: false, value: 'rule3' }) // 修改writable爲true會拋出類型錯誤異常 Object.defineProperty(rules, 'rule3', { writable: true }) Object.defineProperty(rules, 'rule4', { configurable: false, writable: true, value: 'rule4' }) // 能夠修改writable爲false Object.defineProperty(rules, 'rule4', { writable: false }) Object.getOwnPropertyDescriptor(rules, 'rule4') // Object {value: "rule4", writable: false, enumerable: false, configurable: false}
若是數據屬性是不可配置且不可寫的,則不能修改他的值;若是是可配置但不可寫,則能夠修改他的值(其實是先將它標記爲可寫的,而後修改它的值,最後再將它標記回不可寫)。
其實這裏所說的修改值,是經過Object.defineProperty
或Object.defineProperties
方法修改。經過直接賦值的方法在數據屬性不可配置的狀況下是不能修改屬性值的。
Object.defineProperty(rules, 'rule5', { configurable: false, writable: false, value: 'rule5' }) // 修改屬性值會拋出類型錯誤異常 Object.defineProperty(rules, 'rule5', { value: 'rule55' }) // Uncaught TypeError: Cannot redefine property: rule5 rules.rule5 = 'rule55' // 值沒有被修改,也不會拋出異常 rules.rule5 // 'rule5' Object.defineProperty(rules, 'rule6', { configurable: true, writable: false, value: 'rule6' }) // 修改屬性值 Object.defineProperty(rules, 'rule6', { value: 'rule66' }) rules.rule6 // 'rule66' rules.rule6 = 'rule6' // 值沒有被修改,也不會修改 rules.rule6 // 'rule6'
只指定
get
不能寫,若是嘗試對該屬性賦值,會拋出類型錯誤異常。(紅寶書上說只有在嚴格模式下才拋出異常)
Object.defineProperty(rules, 'rule7', { get: function() { return this.common } }) rules.rule7 = 'rule7' // Uncaught TypeError: Cannot redefine property: rule7
只指定
set
不能讀,若是嘗試讀取該屬性值,返回undefined。(紅寶書上說在嚴格模式下才拋出異常,但沒有)
Object.defineProperty(rules, 'rule8', { set: function() { this.common = 'rule8' } }) rules.rule8 // undefined
若是對象是不可擴展的,則能夠編輯已有的自有屬性,但不能給它添加新屬性。
操做對象可擴展性的API有三個:Object.preventExtensions
、Object.seal
、Object.freeze
。
API 用法
Object.preventExtensions:https://developer.mozilla.org...
Object.seal:https://developer.mozilla.org...
Object.freeze:https://developer.mozilla.org...
Object.isExtensions:https://developer.mozilla.org...
Object.isSealed:https://developer.mozilla.org...
Object.isFrozen:https://developer.mozilla.org...
使用Object.preventExtensions
能夠將對象轉換爲不可擴展。
使用Object.isExtensions
來判斷對象是否可擴展。
var ex = {} Object.defineProperty(ex, 'ex1', { configurable: true, writable: true, value: 'ex1' }) Object.isExtensible(ex) // true Object.preventExtensions(ex) Object.isExtensible(ex) // false // 能夠修改已有的屬性 Object.defineProperty(ex, 'ex1', { writable: 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', { configurable: true, writable: false, value: 'se1' }) Object.isSealed(se) // false Object.seal(se) Object.isSealed(se) // true // 修改已有的屬性會拋出類型錯誤異常 Object.defineProperty(se, 'se1', { writable: 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', { configurable: true, writable: false, value: 'fr1' }) Object.isFrozen(fr) // false Object.freeze(fr) Object.isFrozen(fr) // true // 修改已有的屬性會拋出類型錯誤異常 Object.defineProperty(fr, 'fr1', { writable: 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', { configurable: true, value: 'set1' }) Object.defineProperty(set, 'set2', { configurable: true, set: function(val) { this.set1 = val } }) Object.isFrozen(set) // false Object.freeze(set) Object.isFrozen(set) // true set.set2 = 'set2' set.set1 // 'set1'
我對屬性描述符很不熟悉,主要是由於平時用得少。不過最近,開始學寫一些小的庫(雖然很挫),就感受屬性描述符有使用的場景了。我暫時能想到的就是將庫對象的一些屬性設置爲只讀,以防止對象的一些屬性被用戶重寫覆蓋了。還有一個用法是在知乎和學vue的時候知道的,就是經過getter
和setter
實現「監聽」對象屬性的數據更新(在這裏挖一個坑。後面學習一下這種方法,再寫一篇「監聽」對象屬性的數據更新的文章)。最後,若是你們知道更多屬性描述符的使用後場景,但願你們能在評論區留下大家的高見。