天天看點

Object.defineProperty 與 Proxy 有什麼差別?

作者:前端西瓜哥

大家好,我是前端西瓜哥,今天來看看 Object.defineProperty 和 Proxy 的差別。

我們先簡單看看 Object.defineProperty 和 Proxy 的用法。

Object.defineProperty

Object.defineProperty 可以在對象上修改或新增屬性,并設定它的屬性描述符,然後傳回這個對象。

這個方法依次接受:

  1. 要改變的對象
  2. 屬性名
  3. 屬性描述符
const obj = {};

Object.defineProperty(obj, 'a', {
  value: '前端西瓜哥'
});

console.log(obj.a);
// 前端西瓜哥           

屬性描述符是一個對象,有以下幾個配置:

  1. value:屬性值,預設為 undefined;
  2. configurable:屬性描述符能否改變,以及屬性能否被删除(通過 delete 關鍵字)。但 false 下,writable 可以單向變成 false,以及 value 可以改為任何值。預設為 false;
  3. writable:值能否被修改,預設為 false;
  4. get:getter 函數,當屬性被讀取時,調用該函數并使用它的傳回值作為讀取值。我們可以通過 this 通路目前對象。get/set 不能和 value/writable 共存,因為它們互相沖突。預設值為 undefined;
  5. set:setter 函數,當屬性被修改時,設定的新值會傳給 setter 函數,我們就可以将這個新值緩存起來,預設值為 undefined;
  6. enumerable:是否為可枚舉屬性,可枚舉代表可以被 for...in 等 API 讀取到。預設值為 false。

屬性描述符還是有點複雜的,想深入學習可以去看看 MDN 文檔。

Object.defineProperty 可以實作 代理,當我們通過 obj.key 的形式讀取或設定值時,就可以通過 setter 和 getter 去執行一些副作用。

Vue2 正是用這個方式來實作資料的響應式,按需更新視圖的。

Proxy

Proxy 用于建立對象的代理。

我們通過 new Proxy 來建立代理對象,構造函數接受:

  1. 被代理的對象
  2. handler 對象,其實就是一個配置對象,可以設定被代理對象的各種行為的代理,這些行為一般都是函數,稱為 trap(捕捉器)。比如對象的屬性被設定或修改時觸發特定的函數。

看個例子:

const obj = {};

const proxyObj = new Proxy(obj, {
  get(target, prop, receiver) {
    return '前端西瓜哥' + prop;
  }
});

console.log(proxyObj.handsome);
// 前端西瓜哥handsome           

當通路代理對象的屬性時,就會執行該 get 函數。

其中 target 為被代理對象,prop 為被通路的屬性名,recevier 為代理對象。然後 get 函數的傳回值就是最後讀取到的值。

這個例子中,當通路一個屬性時,會傳回一個加了字首的屬性名。

除了 get,Proxy 還可以代理其他的行為,比如設定屬性的捕捉器 set、構造函數的捕捉器 construct、delete 操作符的捕捉器 deleteProperty 等等。

非常多,就不一個個說明了,讀者可自行前往 MDN 文檔學習。

Vue3 抛棄了 defineProperty,使用了 Proxy 來代理對象屬性。

defineProperty 和 Proxy 的差別

defineProperty 和 Proxy 都可以對屬性進行代理。

代理的粒度不同

defineProperty 隻能代理屬性,Proxy 代理的是對象。

也就是說,如果想代理對象的所有屬性,defineProperty 需要周遊屬性一個個加 setter 和 getter。

而 Proxy 隻需要配置一個可以擷取屬性名參數的函數即可。

當然,如果出現嵌套的對象,Proxy 也是要遞歸進行代理的,但可以做惰性代理,即用到嵌套對象時再建立對應的 Proxy。

是否破壞原對象

defineProperty 的代理行為是在破壞原對象的基礎上實作的,它通常會将原本的 value 變成了 setter 和 getter。

Proxy 則不會破壞原對象,隻是在原對象上覆寫了一層。 當新增屬性時,希望屬性被代理,defineProperty 需要顯式調用該 API,而 Proxy 則可以直接用 obj.key = val的形式;

代理數組屬性

defineProperty 不适合監聽數組屬性,因為數組長度可能很大,比如幾百萬,一個個對索引使用 defineProperty 是無法接受的。

一種方式是重寫數組的 API 方法(比如 splice),通過它們來實作代理,但它是有缺陷的:直接用 arr[1] = 100 無法觸發代理。這是 Vue2 的做法。

另外,我們無法對數組的 length 做代理。這暴露了 defineProperty 的一個缺陷:設定了 configurable 為 false 的屬性無法進行代理。數組的 length 就是這種情況。

Object.defineProperty 與 Proxy 有什麼差別?

Proxy 則沒有這個問題,它隻需要設定一個 setter 和 getter,在屬性變化時,能夠在函數參數上拿到索引值。

代理範圍

defineProperty 隻能代理屬性的 get 和 set。

Proxy 還能代理其他的比如 delete 和 handler.getPrototypeOf() 等方法。

相容性

Proxy 是 ES6 新增的特性,相容性不如 defineProperty。

IE 不支援 Proxy。

且 Proxy 不能被完美 polyfill,因為它是在程式設計語言的層面上的修改。

Proxy 貌似還會有些性能問題,但作為标準,浏覽器會持續做重點性能優化。

結尾

總的來看,Proxy 相比 defineProperty 更适合做代理。

我是前端西瓜哥,歡迎關注我,學習更多前端知識。

繼續閱讀