js代理(Proxy)的作用是对基础操作(取值,赋值,调用函数等)进行自定义。e.g. 你有一个对象obj = {}
,当对其属性赋值时obj.age = 5
,你希望做一些验证比如age不能赋值为string。这时代理就可以帮上忙。
用法
const p = new Proxy(target, handler);
target:可以是任意对象,包括数组,函数,或者另一个代理
handler:属性是预定义函数的对象,它定义了这个代理的行为。这个对象内的函数叫做陷阱(traps),因为你一旦对代理进行操作,相关的陷阱就会被触发。
来看一个最基本的例子,如上赋值obj.age = 5
const obj = {};
const proxy = new Proxy(obj, {
set(target, key, value, receiver) {
if (key === 'age') {
if (!Number.isInteger(value)) {
return false;
// 或者 throw new TypeError('only allow int');
}
target[key] = value;
return true;
}
}
});
proxy.age = 5; // OK
proxy.age = 'lol'; // 隐式赋值失败,或抛错
set方法必须返回布尔值,表示赋值成功与否。返回false在严格模式下会抛错TypeError
set函数的四个参数:
target:你代理的真正对象,上面就是obj
key: 你访问的代理属性,如上age
value:给上面属性赋的值,如上5
receiver:被调用的对象,要么是代理,要么是继承代理的对象。一般情况下,如上,显而易见就是proxy,但是某些情况下代理并不是最开始被调用的对象。e.g. 假设你赋值p.age = 5,p不是代理,p自己也没有age属性,但是p的原型链上有个代理,所以当那个代理的set被调用时,receiver其实是p。
ps:赋值和返回true的的那句有些人喜欢写成
return Reflect.set(target, key, value, receiver);
上面这个基本等价于target[key] = value; 但是它返回设置成功与否,所以省了一步哈。
主要的陷阱函数
上面说了set,基本上所有对对象进行基本操作的方法都有相应的trap。这里只谈一下has(), get(), apply(), construct()
has
const obj = {_secret: 'hidden property', show: 'show'};
const proxy = new Proxy(obj, {
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
});
'_secret' in obj // false
'show' in obj // true
这个trap是给in用的(不是for in),Reflect.has()也会触发,上面例子基本上自我解释了,很容易懂。必须返回boolean。
get
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
p.foo或p[bar]都会触发。可以返回任意值。
apply
var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});
p(),p.call(this), p.apply(this)都可触发,可以返回任意值。
construct
var p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
}
});
new一个实例的时候触发,必须返回一个对象。
newTarget:被调用的构造器,如上p
应用
实现一个简单的双向绑定
<body>
<input type="text" id="model">
<p id="word"></p>
</body>
<script>
const model = document.getElementById("model")
const word = document.getElementById("word")
var obj= {};
const newObj = new Proxy(obj, {
set: function(target, key, value, receiver) {
if (key === "text") {
model.value = value;
word.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
// change ui will change local object
model.addEventListener("keyup",function(e){
newObj.text = e.target.value
})
// later on, if you want to programmaticly change local object, it will also reflect on UI
newObj.text = 'something i like';
</script>
取得输入框和显示框元素,给输入框加监听,如果用户输入了,那么对代理的text属性赋值, 这是ui改js,如果后面用户想直接修改newObject,其改动也会反映在UI上,因为代理内部有给元素赋值。