Proxy 的性能,可能比 DefineProperty 更差

老有人跑來(lái)跟我說(shuō) Proxy 和 defineProperty 相比,是性能的巨大提升。我一聽(tīng),這不對(duì)勁啊,跟我學(xué)的知識(shí)不太一樣,我明明記得Proxy 性能比 defineProperty 更差。
所以我就寫(xiě)了幾個(gè)簡(jiǎn)單的例子來(lái)驗(yàn)證一下。
這個(gè)例子的邏輯非常簡(jiǎn)單,我們?cè)诖髷?shù)據(jù)量循環(huán)的過(guò)程中,分別用 Object.defineProperty 與 Proxy 劫持的數(shù)據(jù),執(zhí)行一次 getter 與 setter。然后利用 performance.now 記錄執(zhí)行時(shí)間。
先看 defineProperty 的案例。
首先定義一個(gè)簡(jiǎn)單對(duì)象。
// 在循環(huán)中,我們會(huì)執(zhí)行計(jì)算操作
var target = {
total: 0
}然后另外定義一個(gè)普通變量用于存儲(chǔ)劫持過(guò)程中訪(fǎng)問(wèn)和設(shè)置的值。
然后用 Object.defineProperty 劫持 target。
Object.defineProperty(target, 'count', {
get: function () {
return b;
},
set: function (value) {
b = value;
},
});然后循環(huán) 1000000 次,并打印執(zhí)行時(shí)間。
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += target.count;
target.count = index;
}
console.log('defineProperty', performance.now() - now)接下來(lái)看使用 Proxy 的案例。
也是首先定義一個(gè)普通對(duì)象。
var target = {
count: 0
}然后使用 Proxy 代理。
let proxy = new Proxy(target, {
get: (target, prop, receiver) => {
return Reflect.get(target, prop, receiver)
},
set(target, prop, value) {
return Reflect.set(target, prop, value)
}
});然后循環(huán)訪(fǎng)問(wèn) getter 和 setter。
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
proxy.count
}
console.log('Proxy', performance.now() - now)完整代碼如下:
<script>
var end = 1000000
var b = 0;
var target = {
count: 0
}
Object.defineProperty(target, 'count', {
get: function () {
return b;
},
set: function (value) {
b = value;
},
});
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += target.count;
target.count = index;
}
console.log('defineProperty', performance.now() - now)
</script><script>
var end = 1000000
var target = {
count: 0
}
let proxy = new Proxy(target, {
get: (target, prop, receiver) => {
return Reflect.get(target, prop, receiver)
},
set(target, prop, value) {
return Reflect.set(target, prop, value)
}
});
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
}
console.log('Proxy', performance.now() - now)
</script>我的測(cè)試電腦如下,性能強(qiáng)悍,對(duì)應(yīng)的瀏覽器都是最新版。因此這里我們都定義的是 1000000 萬(wàn)次的執(zhí)行,以更加方便的放大差異。

理論上絕大多數(shù)客戶(hù)的電腦性能都很差,特別是許多面向 B 端的客戶(hù),所以如果有條件的朋友可以用客戶(hù)的環(huán)境來(lái)做一下測(cè)試看看客戶(hù)電腦上的真實(shí)差異
在 chrome 中執(zhí)行結(jié)果為:

我連續(xù)執(zhí)行了 10 次,發(fā)現(xiàn)執(zhí)行結(jié)果都相差不大,執(zhí)行時(shí)間上,Proxy 用時(shí)更久。
然后我切換瀏覽器,在 safari 中執(zhí)行同樣的代碼,執(zhí)行結(jié)果如下:

結(jié)果沒(méi)想到,在 safari 瀏覽器中,Proxy 的性能?chē)?yán)重低于 defineProperty。
然后我又把代碼發(fā)給群友,群友用 QQ 瀏覽器執(zhí)行了一下。

萬(wàn)萬(wàn)沒(méi)想到的是,firefox 的執(zhí)行結(jié)果差異最大。

然后我又嘗試讓 Proxy 代理的對(duì)象增加層級(jí),然后進(jìn)行 set 操作。
注意,這里只是簡(jiǎn)單的增加對(duì)象復(fù)雜度,并不代表更深層級(jí)的屬性也能被代理。
var target = {
count: 0,
b: {
c: 0
}
}for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
proxy.b.c = target.count
}驗(yàn)證結(jié)果發(fā)現(xiàn),當(dāng)層級(jí)變深,執(zhí)行消耗的時(shí)間越長(zhǎng)。下圖是 chrome 的執(zhí)行結(jié)果。

結(jié)論
在常用的幾種瀏覽器中,測(cè)試結(jié)果比較統(tǒng)一,Proxy 的性能都弱于 defineProperty,在 safari,firefox 中,defineProperty 的性能大幅度領(lǐng)先。
當(dāng) Proxy 的目標(biāo)對(duì)象深層次 getter/setter 時(shí),會(huì)增加更多的性能損耗。
針對(duì) Proxy 的性能,chrome 優(yōu)化做得最好。但依然小幅度弱于 defineProperty。
針對(duì)于 defineProperty 的性能,firefox 和 safari 做得比較好,大幅度領(lǐng)先其他瀏覽器。


























