Proxy
对象是ES6加入的新特性,使用它来监听拦截对象的操作,可以使我们完成很多原本javascript
无法完成的特性,在不长的scala语言
学习过程中,发现这门语言有一个很神奇的通配符_
,在函数链式调用中,_
就代表了对象本身,这使得函数式编程的语法更加简洁,放在javascript中举例,就类似这个样子:1
[1, 2, 3, 4].map(_.toString());
就是对每个传来的对象,调用他们自身的toString()
方法,使用ES6中的Proxy
对象,可以很容易地模拟该特性
(nodejs v6.0已支持Proxy
,代码亦可在最新版chrome中运行)1
2
3
4
5
6
7
8
9
10
11
12var _ = new Proxy({}, {
get: function (target, key) {
return function (obj) {
if (obj && obj[key] && typeof obj[key] !== 'function') return obj[key];
var args = arguments;
return function (obj) {
if (obj && obj[key] && typeof obj[key] === 'function')
return obj[key].call(obj, ...args);
};
};
}
});
除了调用对象自带的方法外,还有取出属性的功能,运行结果如下
1 | [1, 32, 128, 1024].map(_.toString('2')); |
2016-6-8续
之前研究Proxy
的时候写出来的那个_
实际上还是有很大缺陷的,比如只能取一层的结构,参数的属性与方法同名时会取出参数的属性,不能正确捕捉到类型不对之类的错误,以及其他等等,于是就想做一个更完善的,现在想想当时的思路本身不太对,返回了一个函数就等于是限制了这个_
的作用层数,不能继续往里面走,解决的方法当然是继续返回一个Proxy
,思路如下:
_
本身返回一个Proxy,取属性操作返回的也是一个Proxy
,这些Proxy
指向一个函数,并在被调用时按顺序取出第一个参数里的属性_
可以预接收参数,接收的参数将在Proxy
被调用时传给取出来的函数
难点是如何知道Proxy
是在预传参的时候被调用还是在真正取值的时候被调用,实际上用返回函数的方法可能很难解决这种问题,在再次深入研究了Proxy
的相关资料后,我意识到上述的两种情况,对应的this
应该是不同的,后来又发现过长的调用链每次都要经过多个Proxy
可能会影响效率,我又改成另外一种形式实现:Proxy
负责收集参数和属性名,并在最终调用的时候新建一个Function
,将整个调用链还原回原本的格式(想到这里我就感觉坑了,实际上我只是把 s=>s.xx.yy.zz()
给简化成 _.xx.yy.zz()
而已,因此我特意加上了一个默认值的功能,好歹比箭头函数多了个错误捕获),再基于其他的一些需求,修修改改之后,变成了以下的格式:
1 | /** |
测试代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var _1 = require('../index')(undefined);
var _2 = require('../index')();
var _3 = require('../index')({foo: 'bar'});
Promise.resolve({a: 12333})
.then(_1.ab.toString().split('')[0].toString().replace('1', 'replaceStr').length)
.then(console.log.bind(null, 'result 1:'), console.error.bind(null, 'catch error1:'))
.then(()=>({a: 12333}))
.then(_2.ab.toString().split('')[0].toString().replace('1', 'replaceStr').length)
.then(console.log.bind(null, 'result 2:'), console.error.bind(null, 'catch error2:'))
.then(()=>({a: 12333}))
.then(_3.ab.toString().split('')[0].toString().replace('1', 'replaceStr').length)
.then(console.log.bind(null, 'result 2:'), console.error.bind(null, 'catch error2:'));
本段代码已经做为模块提交到npm上