Object.defineProperty()和Proxy相關

patrick_kibo發表於2019-04-14

Object.defineProperty()和Proxy相關

Vue3.0版本會將資料劫持的方式從Object.defineProperty切換為Proxy,所以找了時間重新回顧了一下屬性描述,並瞭解了以下Proxy

1. Object.defineProperty

給物件的屬性設定屬性描述,接受三個引數

/*
* obj:需要定義屬性的物件
* prop:定義描述的屬性名
* descriptor: 屬性描述
*/
Object.defineProperty(obj, prop, descriptor);
複製程式碼

1.1 descriptor

用於描述屬性的物件,可以包含以下值

1.1.1 get

屬性在獲取值的時候會呼叫該方法

var obj = {}
Object.defineProperty(obj, 'a', {
    get() {
        console.log('get a');
        return 1;
    }
})
console.log(obj.a)
// 優先輸出'get a', 之後輸出:1
複製程式碼

1.1.2 set

屬性設定值的時候會呼叫該方法

var obj = {}
Object.defineProperty(obj, 'a', {
    set(value) {
        console.log(`set:${value}`);
    }
})
obj.a = 1
// 輸出 `set:1`
複製程式碼

1.1.3 value

用於設定物件屬性的初始值,無法同getset方法同時設定

var obj = {}
Object.defineProperty(obj, 'a', {
    value: 1
})
console.log(obj.a) // 1
複製程式碼

1.1.4 enumerable

設定屬性是否可以列舉,用於for in列舉屬性時候是否可以獲取

var obj = {}
Object.defineProperty(obj, 'a', {
    value: 1,
    enumerable: false, // 設定false無法通過for in獲取
})
console.log(obj.a) // 1
for(let key in obj) {
    console.log(`key:${key}`) // 不會執行
}
複製程式碼

1.1.5 configurable

設定屬性是否可以再次定義屬性描述

var obj = {}
Object.defineProperty(obj, 'a', {
    value: 1,
    configurable: false, // 設定false無法再次配置屬性
})
Object.defineProperty(obj, 'a', {
    value: 2,
})
// 丟擲異常:Uncaught TypeError: Cannot redefine property 'a'
複製程式碼

PS:對於已有屬性可以修改valueenumerablewritable的值(例如:如果obj = {a: 1}這裡修改屬性a的這三個值不會報錯)

1.1.6 writable

設定屬性是否可以賦值,無法同getset方法同時設定

var obj = {}
Object.defineProperty(obj, 'a', {
    value: 1,
    writable: false, // 設定false,無法被普通賦值
})
obj.a = 2
console.log(obj.a) // 1
複製程式碼

1.2 Object.preventExtensions()

阻止物件擴充套件新的屬性,不過並不限制物件原型上的屬性擴充套件

var obj = {a: 1}
Object.preventExtensions(obj)
obj.b = 2 // 嚴格模式下丟擲TypeError異常
console.log(obj) // {a: 1}
複製程式碼

可以使用Object.isExtensible(obj)來判斷是否已經阻止擴充套件了

1.3 Object.seal()

將使得物件禁止擴充套件屬性,同時禁止現有屬性的configurable

var obj = {a: 1}
Object.seal(obj)
console.log(Object.isExtensible(obj)) // false
Object.defineProperty(obj, 'a', {
    value: 2
}) // 丟擲異常
複製程式碼

相當於Object.preventExtensions的基礎上,將已有屬性的configurable都設定為false

var obj = {a: 1}
Object.preventExtensions(obj)
Object.defineProperty(obj, 'a', {
	configurable: false
})
console.log(Object.isSealed(obj)) // true
複製程式碼

可以使用Object.isSealed(obj)判斷是否屬性屬於該情況

1.4 Object.freeze()

Object.seal()的基礎上,將屬性的writable設定為false

var obj = {a: 1}
Object.freeze(obj)
console.log(Object.isSealed(obj)) // true
obj.a = 2
console.log(obj.a) // 1
複製程式碼

相當於

var obj = {a: 1}
Object.seal(obj)
Object.defineProperty(obj, 'a', {
    writable: false
})
console.log(Object.isFrozen(obj)) // true
複製程式碼

可以使用Object.isFrozen(obj)判斷是否屬性屬於該情況

2. Object.defineProperties

Object.defineProperty,可一次性批量定義多個物件屬性

Object.defineProperties(obj, {
    prop1: {
        get() {}
        set() {}
        ...
    },
    prop2: {
        get() {}
        set() {}
        ...
    }
})
複製程式碼

3. Proxy

使用Proxy可以建立一個物件的代理

2.1 new Proxy()

使用new Proxy(target, handler)可以建立物件targetproxy物件,操作proxy物件的時候,根據設定的handler,可以設定物件操作的各時期的具體操作

  1. getPrototypeOf():在呼叫Object.getPrototypeOf()的時候
  2. setPrototypeOf():在使用Object.setPrototypeOf()的時候
  3. isExtensible():在使用Object.isExtensible()的時候
  4. preventExtensions():在使用Object.preventExtensions()的時候
  5. getOwnPropertyDescriptor():在使用Object.getOwnPropertyDescriptor()的時候
  6. defineProperty():在使用Object.defineProperty()的時候
  7. has():在使用in操作符的時候
  8. get():在獲取屬性值的是歐
  9. set():在設定屬性值的時候
  10. deleteProperty():在delete刪除屬性的時候
  11. ownKeys():在Object.getOwnPropertyNames()Object.getOwnPropertySymbols()的時候
  12. apply():在物件作為方法呼叫的時候
  13. construct():在使用new操作符的時候

set舉例說明:

var obj = {}
var proxy = new Proxy(obj, {
    set (target, prop, value) {
        console.log('set value')
        target[prop] = value
    }
})
proxy.a = 1 // 'set value'
console.log(obj) // {a: 1}
複製程式碼

操作proxy物件可以修改對應物件的屬性資訊,但是直接操作target物件,並不會觸發proxy物件中設定的操作:

var obj = {}
var proxy = new Proxy(obj, {
    set (target, prop, value) {
        console.log('set value')
        target[prop] = value
    }
})
obj.a = 1 // 並不會輸出任何資訊
console.log(obj) // {a: 1}
複製程式碼

2.2 Proxy.revocable()

建立一個可以revocable物件,可以在需要廢棄proxy物件的時候銷燬

var revocable = Proxy.revocable({}, {
    set (target, prop, value) {
        console.log('set value')
        target[prop] = value
    }
})
revocable.proxy.a = 1 // 'set value'
revocable.revoke() // 銷燬物件
revocable.proxy.a = 2 // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
複製程式碼

4. 總結

4.1 相同點

從目的來看ProxydefineProperty都是為了擴充套件物件的特性,如果要用來實現MVVM,兩種方案都可以完成

4.2 不同點:

從三個方面來說明

  1. 作用目標不同:defineProperty主要是用於物件定義屬性,注重的是設定物件中屬性的描述,而Proxy用於處理物件,注重的是物件的相關操作
  2. 操作目標不同:defineProperty的時候,是需要直接操作物件本身,來觸發相關屬性設定,而Proxy則需要操作new建立的proxy物件,對原物件操作並不會觸發相關內容
  3. 提供的觸發事件不同:defineProperty只提供了set,get方法可以作為切入口,而Proxy提供了更豐富的物件操作切入口

總的來說vue3.0使用Proxy的目的在於對物件劫持的時候,不用遍歷所有屬性,可以直接使用物件的proxy物件,同時在物件追加屬性的增加劫持的時候,不用再手動使用$set新增劫持

當然和Proxy密切相關的Reflect,這個就留在下次再說了

6. 參考

MDN Object.defineProperty

MDN Proxy

本文存在的問題還望各位指正,謝謝

相關文章