JS屬性特性(屬性描述符)
概念
ECMAScript 5 中定義了一個名叫“屬性描述符”的對象,用于描述了的各種特征。屬性描述符對象有4個屬性:
- configurable:可配置性,控制著其描述的屬性的修改,表示能否修改屬性的特性,能否把屬性修改為訪問器屬性,或者能否通過delete刪除屬性從而重新定義屬性。默認(rèn)值為true。
- enumerable:可枚舉性,表示能否通過for-in遍歷得到屬性。默認(rèn)值為true。
- writable:可寫性,表示能否修改屬性的值。默認(rèn)值為true。
- value:數(shù)據(jù)屬性,表示屬性的值。默認(rèn)值為undefined。
除了上面的屬性,還有兩個存取器屬性,分別是get和set,可以代替value和writable。
- get:在讀取屬性時調(diào)用的函數(shù)。只指定get則表示屬性為只讀屬性。默認(rèn)值為undefined。
- set:在寫入屬性時調(diào)用的函數(shù)。只指定set則表示屬性為只寫屬性。默認(rèn)值為undefined。
使用
“屬性描述符”對象只能在Object.defineProperty或Object.defineProperties中使用。
API 用法
Object.defineProperty:https://developer.mozilla.org...
Object.defineProperties: https://developer.mozilla.org...
- 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
- }
- }
- })
當(dāng)用Object.defineProperty或Object.defineProperties操作(新建或者修改)那些不允許創(chuàng)建或修改的屬性時,會拋出類型錯誤異常。
- // 此例子運行在前面的例子的基礎(chǔ)上
- Object.defineProperty(hello, 'boy', {
- writable: true
- }) // Uncaught TypeError: Cannot redefine property: boy
因為前面boy屬性已經(jīng)被設(shè)置為不可配置,所以這里修改writable會拋出類型錯誤異常。
通過Object.getOwnPropertyDescriptor或者Object.getOwnPropertyDescriptors可以得到屬性描述符。
API 用法
Object.getOwnPropertyDscriptor:https://developer.mozilla.org...
Object.getOwnPropertyDescriptors:https://developer.mozilla.org...
規(guī)則
- 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方法,也不能將它轉(zhuǎn)換為數(shù)據(jù)屬性。
- 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
- // 將它轉(zhuǎn)換為數(shù)據(jù)屬性同樣會拋出類型錯誤異常
- Object.defineProperty(rules, 'rule2', {
- value: 'rule2'
- }) // Uncaught TypeError: Cannot redefine property: rule2
如果數(shù)據(jù)屬性是不可配置的,則不能將它轉(zhuǎn)換為存取器屬性;同時,也不能將它的可寫性從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}
如果數(shù)據(jù)屬性是不可配置且不可寫的,則不能修改他的值;如果是可配置但不可寫,則可以修改他的值(實際上是先將它標(biāo)記為可寫的,然后修改它的值,***再將它標(biāo)記回不可寫)。
其實這里所說的修改值,是通過Object.defineProperty或Object.defineProperties方法修改。通過直接賦值的方法在數(shù)據(jù)屬性不可配置的情況下是不能修改屬性值的。
- 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'
只指定set不能讀,如果嘗試讀取該屬性值,返回undefined。(紅寶書上說在嚴(yán)格模式下才拋出異常,但沒有)
- Object.defineProperty(rules, 'rule7', {
- get: function() {
- return this.common
- }
- })
- rules.rule7 = 'rule7' // Uncaught TypeError: Cannot redefine property: rule7
如果對象是不可擴(kuò)展的,則可以編輯已有的自有屬性,但不能給它添加新屬性。
操作對象可擴(kuò)展性的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可以將對象轉(zhuǎn)換為不可擴(kuò)展。
使用Object.isExtensions來判斷對象是否可擴(kuò)展。
- 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除了可以將對象轉(zhuǎn)換為不可擴(kuò)展的,還可以將對象的所有自有屬性都轉(zhuǎn)換為不可配置的。即不能給對象添加新屬性,而且它已有的屬性也不能刪除或者配置(這里同樣會遵循前面的規(guī)則)。
使用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除了將對象轉(zhuǎn)換為不可擴(kuò)展的和將其屬性轉(zhuǎn)換為不可配置的之外,還可以將自有屬性轉(zhuǎn)換為只讀。(如果對象設(shè)置了set,存取器屬性將不會受影響,仍可以調(diào)用set方法,而且不會拋出異常,但如果set方法是改變該對象的屬性,則不能修改成功)
使用Object.isFrozen來檢測對象是否凍結(jié)(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'
結(jié)語
我對屬性描述符很不熟悉,主要是因為平時用得少。不過最近,開始學(xué)寫一些小的庫(雖然很挫),就感覺屬性描述符有使用的場景了。我暫時能想到的就是將庫對象的一些屬性設(shè)置為只讀,以防止對象的一些屬性被用戶重寫覆蓋了。還有一個用法是在知乎和學(xué)vue的時候知道的,就是通過getter和setter實現(xiàn)“監(jiān)聽”對象屬性的數(shù)據(jù)更新(在這里挖一個坑。后面學(xué)習(xí)一下這種方法,再寫一篇“監(jiān)聽”對象屬性的數(shù)據(jù)更新的文章)。
***,如果大家知道更多屬性描述符的使用后場景,希望大家能在評論區(qū)留下你們的高見。