ES6+ Proxy
1. 前言
本節(jié)我們將學習 ES6 的新增知識點 ——Proxy,Proxy 是代理的意思。Proxy 是一個對象,用于定義基本操作的自定義行為(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。這是 MDN 上的定義,但是不容易理解,想要理解 Proxy 我們首先需要知道什么是代理?
在日常開發(fā)中比較常見的代理常見有,使用 Charles 代理抓包、nginx 服務器的反向代理,以及 VPN 等,都用到了代理。什么是代理呢?我們先看一張圖:
上圖是客戶端訪問網(wǎng)絡的示意圖,客戶端不能直接訪問網(wǎng)絡,它只能先訪問代理服務器,只有代理服務器才能有權限訪問,然后代理服務器把客戶端請求的信息轉發(fā)給目標服務器,最后代理服務器在接收到目標服務器返回的結果再轉發(fā)給客戶端,這樣就完成了整個請求的響應過程。這是現(xiàn)在大多數(shù)服務器的架構,我們可以把上圖的 Proxy Server 理解為 Nginx。代理有正向代理和反向代理,有興趣的小伙伴可以去深入了解一下。
本節(jié)說的 Proxy 就是作用在 JavaScript 中的一種代理服務,代理的過程其實就是一種對數(shù)據(jù)的劫持過程,Proxy 可以對我們定義的對象的屬性進行劫持,當我們訪問或設置屬性時,會去調(diào)用對應的鉤子執(zhí)行。在 ES5 中我們曾學習過 Object.defineProperty()
它的作用和 Proxy 是相同的,但是 Object.defineProperty()
存在一些性能問題,Proxy 對其進行了升級和擴展更加方便和易用。本節(jié)我們將學習 Proxy 的使用。
2. Object.defineProperty()
在學習 Proxy 之前,我們先來回歸一下 ES5 中的 Object.defineProperty()
,接觸過前端框架的同學應該都知道 Vue 和 React,其中 Vue 中的響應式數(shù)據(jù)底層就是使用 Object.defineProperty()
這個 API 來實現(xiàn)的。下面是 Object.defineProperty()
的語法。
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty()
會接收三個參數(shù):
- obj 需要觀察的對象;
- prop 是 obj 上的屬性名;
- descriptor 對 prop 屬性的描述。
當我們?nèi)ビ^察一個對象時需要在 descriptor 中去定義屬性的描述參數(shù)。在 descriptor 對象中提供了 get 和 set 方法,當我們訪問或設置屬性值時會觸發(fā)對應的函數(shù)。
var obj = {};
var value = undefined;
Object.defineProperty(obj, "a", {
get: function() {
console.log('value:', value)
return value;
},
set: function(newValue) {
console.log('newValue:', newValue)
value = newValue;
},
enumerable: true,
configurable: true
});
obj.a; // value: undefined
obj.a = 20; // newValue: 20
上面的代碼中,我們使用一個變量 value 來保存值,這里需要注意的是,不能直接使用 obj 上的值,否則就會出現(xiàn)死循環(huán)。
Object.defineProperty()
是 Vue2 的核心, Vue2 在初始化時會對數(shù)據(jù)進行劫持,如果劫持的屬性還是對象的話需要遞歸劫持。下面我們把 Vue2 中數(shù)據(jù)劫持的核心代碼寫出來。
var data = {
name: 'imooc',
lession: 'ES6 Wiki',
obj: {
a: 1
}
}
observer(data);
function observer(data) {
if (typeof data !== 'object' || data == null) {
return;
}
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = obj[key];
defineReactive(obj, key, value);
}
}
function defineReactive(obj, key, value) {
observer(value);
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
if (newValue === value) return;
observer(newValue);
value = newValue;
}
})
}
上面代碼的核心是 defineReactive 方法,它是遞歸的核心函數(shù),用于重新定義對象的讀寫。從上面的代碼中我們發(fā)現(xiàn) Object.defineProperty()
是有缺陷的,當觀察的數(shù)據(jù)嵌套非常深時,這樣是非常耗費性能的,這也是為什么現(xiàn)在 Vue 的作者極力推廣 Vue3 的原因之一,Vue3 的底層使用了 Proxy 來代替 Object.defineProperty()
那 Proxy 具體有什么好處呢?
3. Proxy
首先我們來看下 Proxy 是如何使用的,語法:
const p = new Proxy(target, handler)
Proxy 對象是一個類,需要通過 new 去實例化一個 Proxy 對象,它接收的參數(shù)比較簡單,只有兩個:
- target:需要使用 Proxy 進行觀察的目標對象;
- handler:對目標對象屬性進行處理的對象,包含了處理屬性的回調(diào)函數(shù)等。
const handler = {
get: function(obj, prop) {
return obj[prop];
},
set: function(obj, prop, value) {
return obj[prop] = value;
}
};
const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, undefined
對比上面的 Object.defineProperty()
API 直觀的看 Proxy 做了一些精簡,把對象、屬性和值作為 get 和 set 的參數(shù)傳入進去,不必考慮死循環(huán)的問題了。這是直觀的感受。
上面我們使用了 Object.defineProperty()
API 簡單地實現(xiàn)了 Vue2 的響應式原理,那么 Vue 使用 Proxy 是怎么實現(xiàn)的呢?它帶來了哪些好處呢?下面我們看實現(xiàn)源碼:
var target = {
name: 'imooc',
lession: 'ES6 Wiki',
obj: {
a: 1
}
}
var p = reactive(target);
console.log(p.name); // 獲取值: imooc
p.obj.a = 10; // 獲取值: {a : 1}
console.log(p.obj.a); // 獲取值: {a : 10}
function reactive(target) {
return createReactiveObject(target)
}
function createReactiveObject(target) {
// 判斷如果不是一個對象的話返回
if (!isObject(target)) return target
// target觀察前的原對象; proxy觀察后的對象:observed
observed = new Proxy(target, {
get(target, key, receiver) {
const res = target[key];
console.log('獲取值:', res)
// todo: 收集依賴...
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
target[key] = value;
}
})
return observed
}
上面的代碼是從 Vue3 中摘出來的 reactive 函數(shù)的實現(xiàn),我們可以直觀地看到?jīng)]有對 target 進行遞歸循環(huán)去創(chuàng)建觀察對象。而且,當我們對 obj 下的 a 屬性設置值時,執(zhí)行 get 函數(shù),這是為什么呢?這就是 Proxy 的優(yōu)點,在對 obj 下屬性設置值時,首先需要調(diào)用 set 方法獲取 target 下 obj 的值,然后判斷 obj 又是一個對象再去調(diào)用 reactive 函數(shù)進行觀察。這樣就不需要遞歸地去對嵌套數(shù)據(jù)進行觀察了,而是在獲取值的時候,判斷獲取的值是不是一個對象,這樣極大地節(jié)約了資源。
4. 小結
本節(jié)主要通過代理和 Object.defineProperty()
API 的學習來理解 ES6 的新增知識點 ——Proxy,并且通過 Vue2 和 Vue3 實現(xiàn)響應式原理來對比 Object.defineProperty()
和 Proxy 的優(yōu)缺點,從而更深入地理解 Proxy。