本文翻譯自https://thecodebarbarian.com/thoughts-on-es6-proxies-performancejavascript
Proxy是ES6的一個強力功能,它經過爲 get/set
一個屬性 設置"陷阱"(函數處理器)讓咱們能夠攔截對於對象屬性的操做。好比:html
const obj = {}; const proxy = new Proxy(obj, { get: () => { console.log('hi'); } }); obj.a; // "hi"
Proxy被稱讚爲如今已經被廢棄的Object.observe()
屬性的取代者。java
然而不幸的是,Proxy有一個致命缺陷:性能。node
更打擊人的是,Object.observe()
就是由於性能被廢棄的,而以我(原做者)對V8的理解,對於JIT(Just in Time,準時制)來講,Object.observe()
比Proxy容易優化多了。git
我(原做者)在node v6.9.0中用benchmark簡單試了一下:es6
var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; var obj = {}; var _obj = {}; var proxy = new Proxy(_obj, { set: (obj, prop, value) => { _obj[prop] = value; } }); var defineProp = {}; Object.defineProperty(defineProp, 'prop', { configurable: false, set: v => defineProp._v = v }); // 譯者注: vanilla js 指的就是原生js suite. add('vanilla', function() { obj.prop = 5; }). add('proxy', function() { proxy.prop = 5; }). add('defineProperty', function() { defineProp.prop = 5; }). on('cycle', function(event) { console.log(String(event.target)); }). on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }). run();
結果以下:github
$ node proxy.js vanilla x 74,288,023 ops/sec ±0.78% (86 runs sampled) proxy x 3,625,152 ops/sec ±2.51% (86 runs sampled) defineProperty x 74,815,513 ops/sec ±0.80% (85 runs sampled) Fastest is defineProperty,vanilla $
從這個簡單的benchmark中咱們能夠看到,Proxy的set
比直接賦值和defineProperty慢很是多(譯者注:ops/sec,每秒進行的操做數,越大越快)。shell
爲防你們好奇,我(原做者)又在node 4.2.1測試了一下Object.observe()
:npm
$ node proxy.js vanilla x 78,615,272 ops/sec ±1.55% (84 runs sampled) defineProperty x 79,882,188 ops/sec ±1.31% (85 runs sampled) Object.observe() x 5,234,672 ops/sec ±0.86% (89 runs sampled) Fastest is defineProperty,vanilla
有些文章可能讓你以爲只要Proxy不用get/set
而是隻設置getOwnPropertyDescriptor()
的話,就比其餘的快,因而我(原做者)又試了試:數組
var _obj = {}; var propertyDescriptor = { configurable: true, set: v => { _obj.prop = v; } }; var proxy = new Proxy(_obj, { getOwnPropertyDescriptor: (target, prop) => propertyDescriptor });
不幸的是,反而更慢了:
$ node proxy.js vanilla x 73,695,484 ops/sec ±1.04% (88 runs sampled) proxy x 2,026,006 ops/sec ±0.74% (90 runs sampled) defineProperty x 74,137,733 ops/sec ±1.25% (88 runs sampled) Fastest is defineProperty,vanilla $
用Proxy包裹一個函數並調用一樣比原生的包裹函數並調用慢很是多:
var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; var fn = () => 5; var proxy = new Proxy(function() {}, { apply: (target, context, args) => fn.apply(context, args) }); var wrap = () => fn(); // add tests suite. add('vanilla', function() { fn(); }). add('proxy', function() { proxy(); }). add('wrap', function() { wrap(); }). on('cycle', function(event) { console.log(String(event.target)); }). on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }). run();
$ node proxy2.js vanilla x 78,426,813 ops/sec ±0.93% (88 runs sampled) proxy x 5,244,789 ops/sec ±2.17% (87 runs sampled) wrap x 75,350,773 ops/sec ±0.85% (85 runs sampled) Fastest is vanilla
目前最有影響力的提高Proxy性能的方法是讓被修改的屬性的configurable設爲false:
var _obj = {}; Object.defineProperty(_obj, 'prop', { configurable: false }); var propertyDescriptor = { configurable: false, enumerable: true, set: v => { _obj.prop = v; } }; var proxy = new Proxy(_obj, { getOwnPropertyDescriptor: (target, prop) => propertyDescriptor });
(譯者注:這段代碼有些問題,enumerable
在configurable
爲false時是無效的)
$ node proxy.js vanilla x 74,622,163 ops/sec ±0.95% (85 runs sampled) proxy x 4,649,544 ops/sec ±0.47% (85 runs sampled) defineProperty x 77,048,878 ops/sec ±0.60% (88 runs sampled) Fastest is defineProperty $
要是這樣寫set/get
,還不如直接用 Object.defineProperty()
。
這樣寫的話,你就不得不設置每一個你要在Proxy中用到的屬性不可配置(not configurable)。
否則的話,V8就會報錯:
var _obj = {}; Object.freeze(_obj); var propertyDescriptor = { configurable: false, enumerable: true, set: v => { _obj.prop = v; } }; var proxy = new Proxy(_obj, { getOwnPropertyDescriptor: (target, prop) => propertyDescriptor }); // Throws: // "TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned // descriptor for property 'prop' that is incompatible with the // existing property in the proxy target" // 攔截'prop'屬性返回的descriptor和target(原對象)已經存在的屬性不匹配 proxy.prop = 5;
Proxy比 Object.defineProperty()
有很多優勢:
Object.defineProperty()
的getter/setter
就不能嵌套,這樣你就不須要知道提早知道你要攔截的全部屬性但它性能太差了。
性能有多大影響呢?
以Promise和回調爲例:
var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; var handleCb = cb => cb(null); // add tests suite. add('new function', function() { handleCb(function(error, res) {}); }). add('new promise', function() { return new Promise((resolve, reject) => {}); }). add('promise resolve', function() { Promise.resolve().then(() => {}); }). on('cycle', function(event) { console.log(String(event.target)); }). on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }). run();
$ node promise.js new function x 26,282,805 ops/sec ±0.74% (90 runs sampled) new promise x 1,953,037 ops/sec ±1.02% (86 runs sampled) promise resolve x 194,173 ops/sec ±13.80% (61 runs sampled) Fastest is new function $
Promise也慢了很是多,
可是 bluebird聲稱爲Promise提供"很是好的性能",測試一下:
$ node promise.js new function x 26,986,342 ops/sec ±0.48% (89 runs sampled) new promise x 11,157,758 ops/sec ±1.05% (87 runs sampled) promise resolve x 671,079 ops/sec ±27.01% (18 runs sampled) Fastest is new function
雖然快了不少,但仍然比回調慢很多。
因此咱們要所以放棄Promise麼?
並非這樣的,不少公司仍然選擇了使用Promise。我(原做者)雖然不是很肯定,可是Uber好像就在使用Promise。
Proxy很慢,可是在你因其性能而放棄它以前,記得一樣性能不好的Promise在最近幾年中被快速採用。
若是你想使用代理,極可能你不會感受到性能的影響,除非你發現本身爲了性能的緣由改變了Promise庫(或者徹底避開了它們)。
2019.01, 在node v11.3.0中: Promise已經變得足夠好, Proxy仍是那樣
vanilla x 833,244,386 ops/sec ±0.76% (89 runs sampled) proxy x 28,590,800 ops/sec ±0.72% (88 runs sampled) wrap x 824,349,552 ops/sec ±0.87% (86 runs sampled) Fastest is vanilla,wrap new function x 834,121,566 ops/sec ±0.82% (89 runs sampled) new promise x 819,789,350 ops/sec ±0.76% (87 runs sampled) promise resolve x 1,212,009 ops/sec ±40.98% (30 runs sampled) Fastest is new function