大家好,我是前端西瓜哥,今天來看看 Object.defineProperty 和 Proxy 的差別。
我們先簡單看看 Object.defineProperty 和 Proxy 的用法。
Object.defineProperty
Object.defineProperty 可以在對象上修改或新增屬性,并設定它的屬性描述符,然後傳回這個對象。
這個方法依次接受:
- 要改變的對象
- 屬性名
- 屬性描述符
const obj = {};
Object.defineProperty(obj, 'a', {
value: '前端西瓜哥'
});
console.log(obj.a);
// 前端西瓜哥
屬性描述符是一個對象,有以下幾個配置:
- value:屬性值,預設為 undefined;
- configurable:屬性描述符能否改變,以及屬性能否被删除(通過 delete 關鍵字)。但 false 下,writable 可以單向變成 false,以及 value 可以改為任何值。預設為 false;
- writable:值能否被修改,預設為 false;
- get:getter 函數,當屬性被讀取時,調用該函數并使用它的傳回值作為讀取值。我們可以通過 this 通路目前對象。get/set 不能和 value/writable 共存,因為它們互相沖突。預設值為 undefined;
- set:setter 函數,當屬性被修改時,設定的新值會傳給 setter 函數,我們就可以将這個新值緩存起來,預設值為 undefined;
- enumerable:是否為可枚舉屬性,可枚舉代表可以被 for...in 等 API 讀取到。預設值為 false。
屬性描述符還是有點複雜的,想深入學習可以去看看 MDN 文檔。
Object.defineProperty 可以實作 代理,當我們通過 obj.key 的形式讀取或設定值時,就可以通過 setter 和 getter 去執行一些副作用。
Vue2 正是用這個方式來實作資料的響應式,按需更新視圖的。
Proxy
Proxy 用于建立對象的代理。
我們通過 new Proxy 來建立代理對象,構造函數接受:
- 被代理的對象
- 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 就是這種情況。
Proxy 則沒有這個問題,它隻需要設定一個 setter 和 getter,在屬性變化時,能夠在函數參數上拿到索引值。
代理範圍
defineProperty 隻能代理屬性的 get 和 set。
Proxy 還能代理其他的比如 delete 和 handler.getPrototypeOf() 等方法。
相容性
Proxy 是 ES6 新增的特性,相容性不如 defineProperty。
IE 不支援 Proxy。
且 Proxy 不能被完美 polyfill,因為它是在程式設計語言的層面上的修改。
Proxy 貌似還會有些性能問題,但作為标準,浏覽器會持續做重點性能優化。
結尾
總的來看,Proxy 相比 defineProperty 更适合做代理。
我是前端西瓜哥,歡迎關注我,學習更多前端知識。