當別人對你說起對象屬性描述符,可能會蒙逼。而若是說起對象屬性的 get/set 方法就秒懂了,標準描述和習慣表述在這裏有些差異,可是指向的是同一個概念所涉及的東西。對象屬性描述符
在編程實踐中是經過 Object 對象的defineProperty
方法暴露給咱們。因此搞清楚Object.defineProperty
是理解對象屬性描述符
的惟一途徑。javascript
Object.defineProperty
, define Property 翻譯成中文就是定義屬性,顧名思義就是爲對象定義或修改屬性的細節,即經過屬性描述符來定義屬性讀寫的細節。使用該方法容許精確添加或修改對象的屬性,熟悉 vue 的朋友對 defineProperty 因該不陌生:vue
Object.defineProperty(obj, prop, descriptor)
defineProperty
接受3個參數, obj 表示要修改或者定義屬性的對象,prop 是要定義或者修改屬性的名稱, descriptor
屬性描述符用於定義該屬性的特性。java
descriptor 是一個對象,對象裏的屬性描述符有兩種類型:數據描述符和存取描述符。編程
數據描述符是一個具備值的屬性,該值多是可寫的,也可能不是可寫的。瀏覽器
存取描述符是由getter-setter函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是二者。框架
數據描述符和存取描述符均具備一下可選鍵值(特性):函數
存取描述符:this
描述符可同時具備的鍵值:翻譯
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
數據描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
若是一個描述符不具備value,writable,get 和 set 任意一個關鍵字,那麼它將被認爲是一個數據描述符。若是一個描述符同時有(value或writable)和(get或set)關鍵字,將會產生一個異常。因此 value、writable 與 get/set 不能同時設置。code
var obj = {} obj.a = 123 Object.defineProperty(obj, "newDataProperty", { value: 101, // 設置值 writable: true, // 值能夠被修改 enumerable: true, // 能夠被枚舉 configurable: true // 屬性能夠被刪除、特性能夠修改 })
上面給對象 obj 添加一個新屬性 'newDataProperty',而且設置了屬性的特性。
在ES5以前對象的屬性咱們只能設置一個字面量值或者一個引用,在瀏覽器支持Object.defineProperty
方法以後,就像給了咱們一臺顯微鏡,可以在更低的粒度層控制屬性的行爲和特性:定義屬性的可訪問行、值的讀寫規則等。
若是對象中不存在指定的屬性,Object.defineProperty()
會建立這個屬性。若是屬性已經存在,Object.defineProperty()
將嘗試根據描述符中的值以及對象當前的配置來修改這個屬性。若是舊描述符將其configurable
屬性設置爲false
,則該屬性被認爲是「不可配置的」,而且沒有屬性能夠被改變(除了單向改變 writable 爲 false)。當屬性不可配置時,不能在數據和訪問器屬性類型之間切換。
描述符中未顯示設置的特性使用其默認值。
下面用幾個栗子來演示這些特性的具體表現:
let foo = { a: 1 } delete foo.a Object.defineProperty(foo, 'b', { value: 2, // 默認值爲2 configurable: false // 不允許被刪除和修改 }) delete foo.b // 沒法刪除 foo.b = 999 // 沒法修改 console.log(foo.b) // 2
let foo = { a: 1, b: 2, c: 3 } for (let i in foo) { // a、b、c能夠被枚舉 console.log(`key: ${i}, value: ${foo[i]}`) } Object.defineProperty(foo, 'a', { enumerable: false // 設置屬性不能夠被枚舉 }) for (let i in foo) { // a沒有被枚舉 console.log(`key: ${i}, value: ${foo[i]}`) }
let foo = { a: 1 } // 修改 foo.a 的值 foo.a = 2 console.log(foo.a) // 2 Object.defineProperty(foo, 'a', { writable: false // 設置值不能被修改 }) // 嘗試修改 foo.a 的值 foo.a = 3 // 沒法修改 console.log(foo.a) // 2
let foo = {} Object.defineProperty(foo, 'a', { value: 1 // 設置屬性的值爲 1 }) console.log(foo.a) // 1
let foo = { a: 1 } Object.defineProperty(foo, 'b', { get: function () { return `hi, ${this.value}` }, set: function (value) { this.a = value // 將輸入值保存在同對象下屬性 a 裏 this.value = value + 1 } }) console.log(foo.b) // 'hi, undefined' foo.b = 1 console.log(foo.a) // 1 console.log(foo.b) // hi, 2
注意: get沒有參數,set接受實參爲當前設置的值.。在get、set函數內部能夠經過this.value
訪問value
特性,從而經過該特性來獲取或者着設置屬性的值。get/set 經常使用於值依賴內部數據的場合。須要儘可能同時設置get、set。若是僅僅只設置了get,那麼咱們將沒法設置該屬性值。若是僅僅只設置了set,咱們也沒法讀取該屬性的值。
Object.defineProperty
只能設置一個屬性的描述符,當須要設置多個屬性描述符時可使用Object.defineProperties
:
let foo = {} Object.defineProperties(foo, { a: { value: 1, configurable: true }, b: { get: function() { return this.value ? `hi, ${this.value}` : 0 }, set: function(value) { this.value = value + 1 } } }) console.log(foo.a) // 1 console.log(foo.b) // 0 foo.b = 2 console.log(foo.b) // 'hi, 3'
咱們能夠經過Object.getOwnPropertyDescriptor
獲取某一屬性的特性集合:
let foo = { a: 1 } Object.defineProperty(foo, 'a', { value: 2, // 設置值爲 2 writable: false, // 值不可修改 configurable: false // 設置屬性不可刪除,特性不可修改 }) let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a') console.log(fooDescripter) // 獲取的特性以下 // { // configurable:false, // enumerable:true, // value:2, // writable:false // }
這裏須要注意,Object.defineProperty
建立一個對象的新屬性與修改一個已經存在屬性的區別。建立一個新屬性默認描述符的鍵值都是 false 或者 undefined。而修改一個已經存在的屬性的描述符時,若是以前沒有被設置過或過原始方式給對象添加的屬性,則屬性的 configurable、enumerable、writable 描述符都默認爲 true。具體差別舉個例子細細體會:
let foo = {} Object.defineProperty(foo, 'a', { value: 2 // 設置值爲 2 }) let fooDescripter = Object.getOwnPropertyDescriptor(foo, 'a') console.log(fooDescripter) // 獲取的特性以下 // { // configurable:false, // 不允許被刪除和修改 // enumerable: false, // 不能被枚舉 // value:2, // writable:false // 值不可修改 // }
變量 a 是經過 Object.defineProperty
方法建立的,默認全部屬性描述符的值都爲 false。 咱們能夠經過最後兩個代碼示例體會一下區別:enumerable
屬性描述符在兩個例子中都沒有被事先設置,可是不一樣情形下的值不同。
原則上這個系列不會去講某個API,可是屬性描述符
可以加深咱們對 javascript 、框架底層的理解。