近期参加的笔试过程中碰到了一道很有意思的题目:
实现一个 add
对象,通过链式传入属性求和返回结果,例如以下示例:
const result1 = add[1][2] + 3; // 6
const result2 = add[1][2][3] + 4; // 10
const result3 = add[1][2][3][4] + 10; // 20
相信你看到这个题目会联想到函数柯里化:
func(1, 2); // 3
func(1, 2, 3); // 6
func(1, 2)(3); // 6
func(1, 2)(3)(4); // 10
然而,函数柯里化的实现是基于函数的,调用的时候使用()
,而这里我们使用的是 []
的方式。
那么我们该如何去实现这个 add
呢?
我们需要访问一个属性,去触发特定的方法,相信熟悉 ES6
的读者可能会想到一个特性:Proxy
。没错,就是它!!!
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
使用方法很简单:
const p = new Proxy(target, handler);
其中 target
是我们要代理的对象,handler
则是以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。详细介绍请看 MDN 文档。
在我们这个 add
对象中,我们是访问任意属性的时候触发对应的方法,即对应的是 handler.get
。
让我们来看看 handler.get
的使用:
const p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
其中 target
是目标对象,property
是被获取的属性名,receiver
是 Proxy 或者继承 Proxy 的对象。
以下代码演示如何拦截属性值的读取操作。
const p = new Proxy({}, {
get: function(target, prop, receiver) {
console.log("called: " + prop);
return 10;
}
});
console.log(p.a); // "called: a"
// 10
这里对一个空对象 {}
进行代理,在 get
方法里输出要访问的属性,然后返回 10。所以说,当我们当问代理对象的属性时,即使该属性不存在,我们依然能返回值。否则,正常情况下我们访问一个不存在的属性会返回 undefined
。
那么接下来我们来看看 add
对象的实现:
const source = { sum: 0 };
const add = new Proxy(source, {
get: function(target, property, receiver) {
// 遇到 + 的操作,会触发隐式类型转换(Symbol.toPrimitive)
if (property === Symbol.toPrimitive) {
// 将之前计算过的所有和进行返回,用于后续运算
const tmp = target.sum;
// 清空之前的值,不影响后续代理器的访问
target.sum = 0;
// Symbol.toPrimitive 方法是内部属性,所以需要返回一个函数
return () => tmp;
} else {
// 简单的访问属性,直接累加
target.sum += Number(property);
return receiver;
}
}
});
source
对象来作为被代理的对象,同时 sum
属性存储累加的结果。source
对象并返回给 add
。get
方法,我们需要判断两种情况:一个是正常的属性访问,另一个是碰到加减法操作时,会进行隐式类型转换成原始数据类型。sum
,然后继续返回代理对象。sum
,最后返回一个 函数(Symbol.toPrimitive
方法是内部属性,所以需要返回一个函数)如此我们便实现了我们的最终目的。