08-代理
21.4 代理
代理是ES6的新特性,它提供了额外的元编程功能(元编程是指程序自我修改的能力)。
对象代理本质上是指拦截和(可选地)修改该对象上的操作的能力。以一个简单的例子开始:修改属性访问权限。下面看一个包含一些属性的常规对象:
const coefficients = {
a: 1,
b: 2,
c: 5,
};
假设该对象的属性表示一个数学方程的系数。那么可能会这样使用它:
function evaluate(x, c) {
return c.a + c.b * x + c.c * Math.pow(x, 2);
}
到目前为止一切都还正常,至此,可以把一个二次方程的系数存储在对象中,然后计算该方程的值(x可以是任何数字)。不过,如果传入的对象缺少一个系数会怎么样呢?
const coefficients = {
a: 1,
c: 3,
};
evaluate(5, coefficients); // NaN
可以通过将coefficients.b设置为0来解决这个问题,不过代理为开发人员提供了更好的方案。因为代理可以拦截操作,从而确保未定义的属性返回0。下列代码给coefficients创建一个代理对象:
const betterCoefficients = new Proxy(coefficients, {
get(target, key) {
return target[key] || 0;
},
});
在作者写这本书的时候,Babel还不支持代理。不过最新版的Firefox已经开始支持代理了,所以可以用Firefox来测试这些示例代码。
代理构造方法的第一个参数是要代理的目标,或者被代理的对象。第二参数是处理器,它可以指定要拦截的动作。在这种情况下,只拦截了get方法(要把它和get属性访问器区分开:它对一般的属性和get访问器都有效)表示的属性访问。get函数接收三个参数(只用了两个):它们分别是目标、属性的key(字符串或符号),以及接收者(代理自身,或某个继承于代理的东西)。
在这个例子中,只检查了目标对象中是否包含指定的key,如果没有,就返回0。运行代码后的结果如下:
betterCoefficients.a; // 1
betterCoefficients.b; // 0
betterCoefficients.c; // 3
betterCoefficients.d; // 0
betterCoefficients.anything; // 0
实际上已经给 coefficients 对象创建了一个代理,这个代理使 coefficients 对象有无穷多的数值属性(都为0,除了定义的属性之外)!
还可以进一步修改代理,使它只处理单个小写字母为key的属性:
const betterCoefficients = new Proxy(coefficients, {
get(target, key) {
if(!/^[a-z]$/.test(key)) return target[key];
return target[key] || 0;
},
});
与其简单地校验 target[key] 是否为真,还不如在结果不是数字时,直接返回0。这个实现就留做读者练习吧。
同样地,可以使用set处理器来拦截被设置的属性(或访问器)。比如,有一个对象,其中包含了一些危险的属性。而开发人员不希望外界直接设置这些属性,或者调用某些方法,除非有额外的设置。使用的额外设置是一个叫作allowDangerousOperations的属性,要先将它设置为true,才能访问这些危险的属性。
const cook = {
name: "Walt",
redPhosphorus: 100, // 危险
water: 500, // 安全
};
const protectedCook = new Proxy(cook, {
set(target, key, value) {
if(key === 'redPhosphorus') {
if(target.allowDangerousOperations)
return target.redPhosphorus = value;
else
return console.log("Too dangerous!");
}
// all other properties are safe
target[key] = value;
},
});
protectedCook.water = 550; // 550
protectedCook.redPhosphorus = 150; // Too dangerous!
protectedCook.allowDangerousOperations = true;
protectedCook.redPhosphorus = 150; // 150
讲到这里,我们也只是触到了代理的冰山一角。想要一探究竟的话,建议可以从Axel Rauschmayer’s的文章“使用ECMA-Script 6代理进行元编程”开始,(http://www.2ality.com/2014/12/es6-proxies.html),后续可以阅读MDN文档(http://mzl.la/1QZKM7U)。
在作者写这本书的时候,Babel还不支持代理。不过最新版的Firefox已经开始支持代理了,所以可以用Firefox来测试这些示例代码。