JS屬性特性(屬性描述符)

概念

ECMAScript 5 中定義了一個名叫「屬性描述符」的對象,用於描述了的各類特徵。屬性描述符對象有4個屬性:javascript

  • configurable:可配置性,控制着其描述的屬性的修改,表示可否修改屬性的特性,可否把屬性修改成訪問器屬性,或者可否經過delete刪除屬性從而從新定義屬性。默認值爲truevue

  • enumerable:可枚舉性,表示可否經過for-in遍歷獲得屬性。默認值爲truejava

  • writable:可寫性,表示可否修改屬性的值。默認值爲true函數

  • value數據屬性,表示屬性的值。默認值爲undefined學習

除了上面的屬性,還有兩個存取器屬性,分別是getset,能夠代替valuewritablethis

  • get:在讀取屬性時調用的函數。只指定get則表示屬性爲只讀屬性。默認值爲undefinedcode

  • set:在寫入屬性時調用的函數。只指定set則表示屬性爲只寫屬性。默認值爲undefined對象

使用

「屬性描述符」對象只能在Object.definePropertyObject.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.definePropertyObject.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}

若是存取器屬性是不可配置的,則不能修改getset方法,也不能將它轉換爲數據屬性。

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.definePropertyObject.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.preventExtensionsObject.sealObject.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的時候知道的,就是經過gettersetter實現「監聽」對象屬性的數據更新(在這裏挖一個坑。後面學習一下這種方法,再寫一篇「監聽」對象屬性的數據更新的文章)。最後,若是你們知道更多屬性描述符的使用後場景,但願你們能在評論區留下大家的高見。