講清楚之 javascript 對象屬性描述符

對象屬性描述符

當別人對你說起對象屬性描述符,可能會蒙逼。而若是說起對象屬性的 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函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是二者。框架

數據描述符和存取描述符均具備一下可選鍵值(特性):函數

  • configurable: 若是爲 false,則任未嘗試刪除目標屬性或修改屬性如下特性(writable, configurable, enumerable)的行爲將被無效化,默認值爲 false。
  • enumerable: 是否能枚舉。也就是是否能被for-in遍歷。默認值爲 false
  • writable: 是否能修改值。默認爲 false
  • value: 該屬性的具體值是多少。默認爲 undefined

存取描述符:this

  • get: 目標屬性被訪問就會調回此方法,並將此方法的運算結果返回用戶。默認爲 undefined
  • set: 目標屬性被賦值,就會調回此方法。默認爲 undefined

描述符可同時具備的鍵值:翻譯

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)。當屬性不可配置時,不能在數據和訪問器屬性類型之間切換。

描述符中未顯示設置的特性使用其默認值。

下面用幾個栗子來演示這些特性的具體表現:

configurable

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

enumerable

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]}`)
}

writable

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

value

let foo = {}
Object.defineProperty(foo, 'a', {
    value: 1 // 設置屬性的值爲 1
})
console.log(foo.a) // 1

get/set

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 、框架底層的理解。

相關文章
相關標籤/搜索