這是在瀏覽 Vue.js 原始碼時看到的方法。是在 ECMAScript 5.1 的規格中被定義的。
以 Vue.js 中 /src/core/index.js
裡部分的程式碼為例
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
return this.$vnode && this.$vnode.ssrContext
}
})
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
剛看到方法名時是感到疑惑的,想說物件屬性(Property)[^1]不是用 object['propertyName']
就可以定義了嗎?為什麼要特別有這個方法?接著又看到裡面定義了 get()
方法,似乎有點感覺了,繼續研究下去看看吧。
首先看個 MDN Web docs 關於 Object.defineProperty() 的介紹,這邊只引用署名式(signature),關於參數、返回值的定義,請自行參見該文件。
Object.defineProperty(obj, prop, descriptor)
而平時我們用 object['propertyName'] =
去賦值的行為,等價於透過 Object.defineProperty()
特別設定 descriptor
的行為,如下:
'use strict'
let obj = {}
// 下面兩則敘述在行為上是等價的。
obj['key'] = 'value'
Object.defineProperty(obj, 'key', {
enumerable: true,
configurable: true,
writable: true,
value: 'value'
})
為什麼要說特別設定呢,因為 descriptor
中 enumerable
、configurable
、writable
這些特性(attributes)[^1]的預設值其實都是 false
,如下:
'use strict'
let obj = {}
// 下面兩則敘述在行為上是等價的。
Object.defineProperty(obj, 'key', { value: 'value' })
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'value'
})
那 descriptor
中的這些特性分別代表什麼意思呢?value
較好懂,即為查詢這個屬性會得到的值。但除此之外還有 enumerable
、configurable
、writable
三種特性、以及 get()
、 set()
兩個方法可以定義,但今天先不討論後兩個方法。
因為平時透過 =
賦值的狀況,與特性為 true
時的行為相同,所以這邊以特性為 false
的狀況去介紹,剛好也等於 Object.defineProperty()
的預設值。
enumerable
為 false
時,代表不可列舉。所謂的不可列舉就是當我們呼叫 Object.keys()
或透過 for...in
迴圈時,是找不到該屬性的。
'use strict'
let obj = {}
// enumerable: true
obj['key'] = 'value'
console.log(obj)
// enumerable: false
Object.defineProperty(obj, 'key', { enumerable: false })
console.log(obj)
// enumerable: true
Object.defineProperty(obj, 'key', { enumerable: true })
console.log(obj)
// Output:
// { key: 'value' }
// {}
// { key: 'value' }
writable
為 false
時,代表不可覆寫。所謂的不可覆寫就是該屬性無法重新賦值,不管你在設定 writable
時,該屬性有沒有值,在設定後都無法透過 =
重新賦值,除非再用 Object.defineProperty()
去設定。
'use strict'
let obj = {}
// writable: true
obj['key'] = 'value'
console.log(obj['key'])
obj['key'] = 'value1'
console.log(obj['key'])
Object.defineProperty(obj, 'key', { value: 'value2' })
console.log(obj['key'])
// writable: false
Object.defineProperty(obj, 'key', { writable: false })
// Available
Object.defineProperty(obj, 'key', { value: 'value3' })
console.log(obj['key'])
// Unavailable
obj['key'] = 'value4'
// Output:
// static-value
// static-value1
// static-value2
// static-value3
// TypeError: Cannot assign to read only property 'key' of object
configurable
為 false
時,代表不可再設定。所謂的不可再設定就是若要再透過Object.defineProperty()
變動 descriptor
中的 enumerable
、configurable
、get()
、set()
的值時,都會跳出 TypeError
。而由於後兩者是透過參照的方式,所以就算重新給一個敘述完全一樣的函式,都算是變動了參照,也會回報錯誤。
那 writable
和 value
都是可以再設定的嗎?是的,但是這是在 writable
為 true
的情況。若在 configurable
與 writable
皆為 false
的情況,透過 Object.defineProperty()
變動兩者的值或是透過 =
更改 value
的值時,一樣都會出現 TypeError
。這部分要特別注意,我自己也是被這件事困惑過。
'use strict'
let obj = {}
// configurable: true
// writable: true
obj['key'] = 'value'
console.log(obj)
// Available
Object.defineProperty(obj, 'key', { value: 'value1' })
console.log(obj)
// configurable: false
// writable: true
Object.defineProperty(obj, 'key', {
configurable: false,
writable: true ,
enumerable: true
})
// Available
obj['key'] = 'value2'
console.log(obj)
// Available
Object.defineProperty(obj, 'key', { value: 'value3' })
console.log(obj)
// configurable: false
// writable: false
Object.defineProperty(obj, 'key', { writable: false })
// Unavailable
obj['key'] = 'value4'
console.log(obj)
// Unavailable
Object.defineProperty(obj, 'key', { value: 'value5' })
console.log(obj)
// Unavailable
Object.defineProperty(obj, 'key', { configurable: true })
// Unavailable
Object.defineProperty(obj, 'key', { writable: true })
// Output:
// { key: 'value' }
// { key: 'value1' }
// { key: 'value2' }
// { key: 'value3' }
// TypeError: Cannot assign to read only property 'key' of object '#<Object>'
// TypeError: Cannot redefine property: key
// TypeError: Cannot redefine property: key
// TypeError: Cannot redefine property: key
關於 descriptor 裡的 enumerable
、configurable
、writable
, value
這四種特性大致研究到這邊。明天再來繼續研究 get()
、set()
,以及 Object.defineProperty()
特別適合運用在哪些情境中吧。
1: properties 和 attributes 在英中翻譯上通常都會翻成屬性,由於本文兩者都有出現,所以在翻譯上會特別區別。Properties 取其有擁有(own)之意,且在這裡的確是特別指 Object 擁有的資料,所以維持「屬」性的翻譯;而這邊的 attributes 特別是指 descriptor 這個 object 裡的屬性,但這些屬性是會影響被敘述物件的行為,所以我會在換句話說時講成設定,或者是採用「特性」作為翻譯。