Proxies
var obj = new Proxy({},{
get: function (target, key, receiver){
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set:function (target, key, value, receiver){
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver)
}
})
看看我们创建的对象:
> obj.count = 1;
setting count!
> ++obj.count;
getting count!
setting count!
2
我们拦截了这个对象的属性获取,重载了"."
运算符。
什么是对象
- 对象有属性。可以获取,设置,删除它们。
- 对象有原型。JS的继承靠这个实现。
- 有些对象是函数或者构造函数。你可以调用它们。
JS程序对对象做的事情,基本都是通过属性、原型和函数。
ECMAScript标准委员会定义了14个内置方法,所有对象的通用接口。[[]]
强调这些是内置方法,一般js代码看不到,不能调用、删除或者改写这些方法。下面列出了几个比较常用的。
obj.[[Get]](key,receiver)
获取某属性的值。 调用:obj.prop
或者obj[key]
。
obj is the object currently being searched; receiver is the object where we first started searching for this property. Sometimes we have to search several objects. obj might be an object on receiver’s prototype chain.
-
obj.[[Set]](key,value,receiver)
给对象的某属性赋值。 调用:obj.prop = value
或者obj[key] = value
。obj.prop += 2
先调用[[Get]]
方法,再调用[[Set]]
方法。++
和--
一样。 -
obj.[[HasProperty]](key)
看某属性是否存在。 调用:key in obj
。 -
obj.[[Enumerate]]()
列出对象的可枚举属性。 调用:for (key in obj) ...
。 返回一个可迭代对象,for-in
循环获取一个对象的属性名。 -
obj.[[GetPrototypeOf]]
返回对象的原型。 调用:obj.__proto__
或者Object.getPrototypeOf(obj)
。 -
functionObj.[[Call]](thisValue,arguments)
调用一个函数。 调用:functionObj()
或者x.method()
可选,不是所有对象都是函数。 -
constructorObj.[[Construct]](arguments,newTarget)
激活一个构造函数。 调用举例:new Date(2019, 9, 10)
。 可选。不是所有对象都是构造函数。
ES6标准语法或内置方法,所有和对象有关的,都是被这14个内置方法确定的。proxy做的的是取代这些方法。
当我们覆盖这些内置方法时,我们会改变像obj.prop
一样的核心语法,Object.keys()
的内置方法,甚至更多。
Proxy
ES6定义了一个新的全局构造函数:Proxy
。它有两个参数,一个目标对象,一个处理对象。
一个简单例子:
var target = {}, handler = {};
var proxy = new Proxy(target,handler);
先不管处理程序对象,只关注proxy和target的关联。
所有proxy的内置方法都传递给target。即,如果啥调用proxy.[[Enumerate]]()
,它返回target.[[Enumerate]]()
。
先试试。调用proxy.[[Set]]()
。
proxy.color = "pink";
发生了什么?
proxy.[[Set]]()
调用了target.[[Set]]()
,所以给target
增加了一个新的属性。
> target.color
"pink"
proxy
基本上和它的target
行为一致。
proxy !== target
。
proxy
有时候会不能通过类型检查,而target
能通过。
即使一个proxy的target是DOM元素,代理也不是真的元素。
document.body.appendChild(proxy)
会失败,报TypeError
。
Proxy handlers
现在来看处理程序对象。这个让proxy有用。
这个处理程序对象的方法会覆盖proxy的任意内置方法。
比如想要拦截给对象属性赋值的其他,可以自己定义一个handler.set()
方法。
var target = {};
var handler = {
set: function (target, key, value, receiver){
throw new Error("不允许给这个对象赋值!")
}
};
var proxy = new Proxy(target, handler);
> proxy.name = "June"
Error:"不允许给这个对象赋值!"
MDN上有处理程序方法的完整列表。 ES6定义了14个内置方法。
例子:“不可能的”自动填充对象
> var tree = Tree();
> tree
{ }
> tree.branch1.branch2.twig = "green";
> tree
{ branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
{ branch1: { branch2: { twig: "green" },
branch3: { twig: "yellow" }}}
中间的对象branch1
,branch2
,branch3
在需要的时候就自动创建了。怎么做到的?
用proxies,几行代码就可以搞定。
function Tree() {
return new Proxy({}, handler);
}
var handler = {
get: function (target, key, receiver) {
if (!(key in target)) {
target[key] = Tree(); // auto-create a sub-Tree
}
return Reflect.get(target, key, receiver);
}
};
注意最后的Reflect.get()
。一个通用需求,代理处理程序方法,告知“现在就做委派给目标的默认行为”。(不知道我在写些什么鬼=-=)