当前位置:嗨网首页>书籍在线阅读

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)。