ES6 Proxy 性能之我见

本文翻译自https://thecodebarbarian.com/thoughts-on-es6-proxies-performance

Proxy是ES6的一个强力功能,它通过为 get/set一个属性 设置"陷阱"(函数处理器)让我们可以拦截对于对象属性的操作。比如:

const obj = {};
const proxy = new Proxy(obj, {
get: () => {
console.log('hi');
}
}); obj.a; // "hi"

Proxy被称赞为现在已经被废弃的Object.observe()属性的取代者

然而不幸的是,Proxy有一个致命缺陷:性能。

更打击人的是,Object.observe()就是因为性能被废弃的,而以我(原作者)对V8的理解,对于JIT(Just in Time,准时制)来说,Object.observe()比Proxy容易优化多了。

Proxy到底有多慢?

我(原作者)在node v6.9.0中用benchmark简单试了一下:

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();

结果如下:

$ 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,每秒进行的操作数,越大越快)。

为防大家好奇,我(原作者)又在node 4.2.1测试了一下Object.observe()

$ 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性能的方法

目前最有影响力的提升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
});

(译者注:这段代码有些问题,enumerableconfigurable为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 也要不行了么?

Proxy比 Object.defineProperty()有不少优点:

  • Proxy 可以嵌套,而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

ES6 Proxy 性能之我见的更多相关文章

  1. Vue3.0 响应式数据原理:ES6 Proxy

    Vue3.0 开始用 Proxy 代替 Object.defineProperty了,这篇文章结合实例教你如何使用Proxy 本篇文章同时收录[前端知识点]中,链接直达 阅读本文您将收获 JavaSc ...

  2. ES6 Proxy和Reflect(下)

    construct() construct方法用于拦截new命令. var handler = { construct (target, args) { return new target(...ar ...

  3. ES6 Proxy和Reflect (上)

    Proxy概述 Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种"元编程"(meta programming),即对编程语言进行编程. Proxy可以理 ...

  4. es6 proxy代理

    es6 新增构造函数 Proxy Proxy 构造函数,可以使用new 去创建,可以往里面插入两个参数,都是对象 let target = {} let handler = {} let proxy ...

  5. es6 Proxy

    proxy在语言层面去操作一个对象 var user={}; user.fname='Bob'; user.lname="Wood"; user.fullName= functio ...

  6. ES6 Proxy的应用场景

    一.相关API Proxy Reflect 二.Proxy应用场景 1.数据校验 表单提交的时候做数据校验,例如年龄是不是满足条件,数据类型是不是满足要求等等,这场场景非常适合使用Proxy. 下面展 ...

  7. es6 Proxy对象详解

    Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改.这个词的原理为代理,在这里可以表示 ...

  8. es6 proxy浅析

    Proxy 使用proxy,你可以把老虎伪装成猫的外表,这有几个例子,希望能让你感受到proxy的威力. proxy 用来定义自定义的基本操作行为,比如查找.赋值.枚举性.函数调用等. proxy接受 ...

  9. 新的知识点来了-ES6 Proxy代理 和 去银行存款有什么关系?

    ES给开发者提供了一个新特性:Proxy,就是代理的意思.也就是我们这一节要介绍的知识点. 以前,ATM还没有那么流行的时候(暴露年纪),我们去银行存款或者取款的时候,需要在柜台前排队,等柜台工作人员 ...

随机推荐

  1. overflow onclick ondblclick 练习

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. 【sqli-labs】 less21 Cookie Injection- Error Based- complex - string ( 基于错误的复杂的字符型Cookie注入)

    这个和less20是一样的,唯一的不同在于添加了括号和使用了base64对cookie进行了编码(因为使用了base64_decode解码函数) admin被编码成了YWRtaW4=但是执行的SQL语 ...

  3. TextInputLayout使用时各个地方的字体颜色

    我们现在在做Android端的输入框时,要具备如下功能: 默认提示获取焦点时提示上移至输入框顶部获取焦点时输入框有提示错误时增加错误提示直接上图: 默认情况: 获取焦点时: 开始输入文字时: 有错误时 ...

  4. 蒟蒻的长链剖分学习笔记(例题:HOTEL加强版、重建计划)

    长链剖分学习笔记 说到树的链剖,大多数人都会首先想到重链剖分.的确,目前重链剖分在OI中有更加多样化的应用,但它大多时候是替代不了长链剖分的. 重链剖分是把size最大的儿子当成重儿子,顾名思义长链剖 ...

  5. [luogu1073 Noip2009] 最优贸易 (dp || SPFA+分层图)

    传送门 Description C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市.任意两个 城市之间最多只有一条道路直接相连.这m 条道路中有一部分为单向通行的道路,一部分 为 ...

  6. Centos 7, Torque 单节点部署

    1.准备工作 安装Torque必须首先配置linux主机名称,服务器主机名称大多默认localhost,不建议直接使用localhost. linux主机名称修改地址:http://www.cnblo ...

  7. python爬虫09 | 上来,自己动 !这就是 selenium 的牛逼之处

    作为一个男人 在最高光的时刻 就是说出那句 之后 还不会被人打 ... 虽然在现实生活中你无法这样 但是在这里 就让你体验一番 那种呼风唤雨的感觉 我们之前在爬取某些网站的时候 使用到了一些 pyth ...

  8. 2.3 SVN在myeclipse中的使用

    一.将svn插件文件夹复制到myeclipse的dropins目录下,并重启myeclipse 二.从SVN中检查项目到myeclipse  2.打开myeclipse,点击window的show v ...

  9. git常见问题总结

    1.每次上传文件的时候,有很多iml文件容易不小心上传上去,然后报错,所以可以把这些文件取消上传 如图所示,每次提交时,都不会显示标红文件 具体操作步骤如下:

  10. 【Codeforces 342A】Xenia and Divisors

    [链接] 我是链接,点我呀:) [题意] [题解] 最后a,b,c只有以下3种情况 1,2,4 1,2,6 1,3,6 那么用cnt[8]统计每个数字出现的次数. 输出cnt[4]次1,2,4 (如果 ...