Object.defineProperty()的使用

Object.defineProperty(),它的做用是能夠經過該API直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。Vue框架內部大量使用了此API爲對象定義屬性,其響應式原理也是經過此API自定義setter與getter而完成的。javascript

在平時的業務開發中,Object.defineProperty()基本上使用不到,由於在對象上定義一個新屬性直接經過.運算符就能夠,例:obj.a = 123。但經過該API能夠對一個對象屬性作更多的事情,好比數據的攔截、自定義setter與getter等。java

概念

Object.defineProperty(obj, prop, descriptor)app

  • obj:要在其上定義屬性的對象
  • prop:要定義或修改的屬性的名稱
  • descriptor:將被定義或修改的屬性描述符

前倆個參數很簡單明瞭,就是指出要在哪一個對象上定義或修改哪一個屬性。重要的是屬性的描述符,當咱們經過.運算符定義或修改屬性的時候,其實等同於調用了Object.defineProperty()。以下:框架

const man = {}
man.name = 'lihaoze'
Object.getOwnPropertyDescriptor(man, 'name')
// {value: "lihaoze", writable: true, enumerable: true, configurable: true}
複製代碼
const man = {}
Object.defineProperty(man, 'name', {
  value: 'lihaoze',
  writable: true,
  configurable: true,
  enumerable: true
})
Object.getOwnPropertyDescriptor(man, 'name')
// {value: "lihaoze", writable: true, enumerable: true, configurable: true}
複製代碼

value顧名思義就是屬性對應的值,但其不必定是必須存在的。它與setter、getter互斥。後面咱們會講到。那writable,enumerable,configurable具體是什麼用處呢,咱們接下來分別介紹。函數

writable

當且僅當該屬性的writable爲true時,value才能被賦值運算符改變。默認爲 false。oop

當咱們經過Object.defineProperty()定義一個屬性不設置writable屬性,或者設置爲false。那麼咱們將不能經過.的方式來修改屬性值。ui

const man = { name: 'lihaoze' }
Object.defineProperty(man, 'age', {
  value: 18,
  writable: false,
  configurable: true,
  enumerable: true
})
man.age = 22
alert(man.age) // 18
複製代碼

能夠看到我永遠都是18歲了😜,但總有人想試圖揭穿我,因此Ta只要再使用該API從新定義value,就能夠修改它。以下this

Object.defineProperty(man, 'age', {
  value: 22,
  writable: false,
  configurable: true,
  enumerable: true
})
alert(man.age) // 22
複製代碼

configurable

當且僅當該屬性的 configurable 爲 true 時,該屬性描述符纔可以被改變,同時該屬性也能從對應的對象上被刪除。默認爲 false。因此,當configurable 爲 false的時候,該屬性的描述符就不能被修改了,也不能被刪除。這是個不可逆的操做。spa

const man = { name: 'lihaoze' }
Object.defineProperty(man, 'age', {
  value: 18,
  writable: true,
  configurable: false,
  enumerable: true
})
man.age = 22
console.log(man.age) // 22
delete man.age
// 沒法刪除該屬性 
console.log(man.age) // 22 
// 嘗試修改enumerable,沒法再次修改描述符 Cannot redefine property: age
Object.defineProperty(man, 'age', {
  value: 18,
  writable: true,
  configurable: false,
  enumerable: false 
})
複製代碼

但這裏有個例外:當writable屬性爲true的時候,是能夠修改爲false的。 看下面代碼:代理

// 這時,只有writable能夠被修改爲false,但false以後,就沒法再修改爲true
Object.defineProperty(man, 'age', {
  value: 18,
  writable: false,
  configurable: false,
  enumerable: true
})
man.age = 22
console.log(man.age) // 18
複製代碼

enumerable

當且僅當該屬性的enumerable爲true時,該屬性纔可以出如今對象的枚舉屬性中。默認爲 false。 能夠直到,這個描述符控制的是屬性是否會出如今對象的屬性枚舉中,好比for..in循環,若是把enumerable設置成false,這個屬性不會出如今枚舉中,雖然仍然能夠訪問它。

const man = { name: 'lihaoze', age: 18 }
Object.defineProperty(man, 'job', {
  value: 'Web Engineer',
  writable: false,
  configurable: true,
  enumerable: false 
})
console.log(man) // {name: "lihaoze", age: 18, job: "Web Engineer"}
console.log(Object.keys(man)) // ["name", "age"]
for (let key in man) {
  console.log(key) // name age
}
複製代碼

set、get

set: 一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。當屬性值修改時,觸發執行該方法。該方法將接受惟一參數,即該屬性新的參數值。

get: 一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined。當訪問該屬性時,該方法會被執行,方法執行時沒有參數傳入,可是會傳入this對象

注意:當定義了一個屬性的set、get描述符,則JavaScript會忽略該屬性的value、writable屬性。也就是說這倆對兒屬於互斥的關係

const man = { name: 'lihaoze', birthYear: 1996 }
// 定義setter、getter,
// get、set方法不能使用自己屬性,會形成堆棧溢出 Maximum call stack size exceeded
Object.defineProperty(man, 'age', {
  configurable: true,
  enumerable: true,
  get() {
    const date = new Date()
    return date.getFullYear() - this.birthYear
  },
  set(val) {
    // Uncaught Error: 沒法修改真實年齡
    throw new Error('沒法修改真實年齡')
  }
})
複製代碼

上面的例子經過出生年份來推算個人年齡,當設置年齡的時候,咱們拋出錯誤,防止被修改。

// 嘗試定義value、writable描述符,會拋出錯誤
// Uncaught TypeError: Invalid property descriptor. 
// Cannot both specify accessors and a value or writable attribute
Object.defineProperty(man, 'age', {
  value: 15,
  configurable: true,
  enumerable: true,
  get() {
    const date = new Date()
    return date.getFullYear() - this.birthYear
  },
  set(val) {
    // Uncaught Error: 沒法修改真實年齡
    throw new Error('沒法修改真實年齡')
  }
})
複製代碼

應用

  • 經過設置set描述符,來終止其餘人修改值,並給出於友好的提示,如上面的代碼⇧。其中Vue內部也是使用這個方法,來給出咱們開發者友好的提示。舉幾個列子:

    // 咱們不用去弄懂defineReactive,只要知道該函數的第四個參數是定義的get函數,
    // 能夠看到Vue在非生產環境,會爲$attrs、props定義get,防止用戶修改該屬性,給出提示。
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(props, key, value, () => {
      if (!isRoot && !isUpdatingChildComponent) {
        warn(
          `Avoid mutating a prop directly since the value will be ` +
          `overwritten whenever the parent component re-renders. ` +
          `Instead, use a data or computed property based on the prop's ` +
          `value. Prop being mutated: "${key}"`,
          vm
        )
      }
    })
    複製代碼
  • 經過設置get描述符,來代理對象上面的值,在Vue中咱們之因此可使用this.xxx訪問各類數據、方法、props、是由於Vue將這些都設置了set,從而代理到其餘私有對象上。看下面代碼:

    const vm = new Vue({
      el: '#app'
      data: {
        msg: 'hello world!'
      }
    })
    vm.msg = vm._data.msg // true
    複製代碼

    其中,Vues是經過proxy函數實現數據代理,Vue部分源碼以下:

    // 定義通用描述符
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    // 設置set、get、實現將this[sourceKey]上的值代理到this[key]
    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    // ... 忽略無關代碼
    proxy(vm, `_data`, key)
    複製代碼
相關文章
相關標籤/搜索