本文是ES2015系列的第五篇文章,讨论一些关于Proxy的使用。通过Proxy 我们可以定制化关于对象的基本操作,比如get或者set方法,枚举对象方法等等,对其行为进行拦截并修改。
类型声明与方法
1. Proxy初始化
我们可以通过下面的方式来创建一个代理器,该代理器包含两个参数 一个是target可以是任意的对象,数组,函数甚至是其他的代理器,另一个参数为handler代表了一个属性方法来定义当特定的操作被触发的时候代理器的行为。
var p = new Proxy(target, handler);
其中我们可以通过设定handler的以下方法来绑定事件处理,通过下面的这些方法我们基本上能劫持对于对象的任何操作行为。
handler.get()
当target调用 getting 方法时的行为.
handler.set()
当target调用 setting 方法时的行为.
handler.deleteProperty()
当target调用 delete 方法时的行为.
handler.getPrototypeOf()
当target调用 Object.getPrototypeOf时的操作
handler.setPrototypeOf()
当target调用 Object.setPrototypeOf时的操作
handler.isExtensible()
当target调用 Object.isExtensible时的操作
handler.preventExtensions()
当target调用 Object.preventExtensions时的操作
handler.getOwnPropertyDescriptor()
当target调用 Object.getOwnPropertyDescriptor时的操作
handler.defineProperty()
当target调用 Object.defineProperty时的操作
handler.has()
当target调用 in 操作符时的行为.
handler.ownKeys()
当target调用 Object.getOwnPropertyNames.方法时的行为.
handler.apply()
当target调用函数 call 方法时的行为.
handler.construct()
当target调用 new 操作方法时的行为.
2. Proxy使用方法
以下测试实例可直接在最新的chrome下面运行,chrome浏览器已经支持proxy操作符的使用,下面的代码中我们定义了handler.get()方法,该方法会在target的属性被访问的时候被触发,当然我们这里只是一个例子,否则我们调用任何不存在的属性的时候均返回27了。
var handler = {
get: function(target, name){
return name in target?
target[name] :
27;
}
};
var Person={
name:"Mike",
city:"Beijing"
};
var p = new Proxy(Person, handler);
console.log(p.name)
console.log(p.age)
//Mike
//27
Proxy使用场景
1. 使用Proxy来实现对象赋值的验证
上面的例子中我们调用了Proxy来捕获get方法,下面我们可以通过设置set方法来验证用户的输入是否正常。比如说我们有一个user对象,在设置user的信息的时候可以使用proxy来验证用户email是否合规,密码是否达到要求的长度等等。
var validator={
set:function(obj,prop,value){
if(prop === 'password'){
if(value.match(/^[\w]{6,12}$/)){
obj[prop]=value;
}else{
throw new Error("password value is illege")
}
}else if(prop === 'email'){
if(value.match(/^\S+@\S+\.\S+$/)) //这只是一个简单的验证
{
obj[prop]=value
}else{
throw new Error("email value is illege")
}
}
}
}
var user={name:"mike"}
var p=new Proxy(user,validator)
通过上面的代码我们创建了一个代理器,我们对于user的访问都要使用这个代理器来操作,这样我们才能实现行为的验证,下面我们来进行一些简单的访问设置操作。我们首先设置对象的password和email,由于格式均正确所以不会报错误出来,此时我们的user对象已经被赋予了新的值。
p.password="1234567"
p.email="1234567@gmail.com"
user
// Object {name: "mike", password: "1234567", email: "1234567@gmail.com"}
我们设置对象的密码为一个短密码,此时经过validator验证失败后报错,无法设置到新的对象,所以我们查看user的时候,仍旧是原来的值,没有变化。同样我们设置email对象也一样,对于验证不能通过的报错。
p.password="123"
// Uncaught Error: password value is illege
user
// Object {name: "mike", password: "1234567", email: "1234567@gmail.com"}
p.email="z[@b](/user/b)"
// Uncaught Error: email value is illege(…)validator.set user
// Object {name: "mike", password: "1234567", email: "1234567@gmail.com"}
若我们直接赋值给user,则无法实现任何验证,设置成功
user.email="z[@b](/user/b)"
"z[@b](/user/b)"
user
//Object {name: "mike", password: "1234567", email: "z[@b](/user/b)"}
2.扩展数组的属性和方法
对于数组对象,有时候我们只想要得到数组中对象的某个键值内容,我们可以编写map函数直接获得,也可以使用lodash或者underscore这样的库函数获得,下面的例子我们使用的是代理的方式,我们只需要在数组对象上使用该键访问就能实现,不过这样去实现的方式有一些奇怪。
var AddSomeFunctionHandler={
get:function(obj,prop){
if(prop in obj){
return obj[prop] // 按数组默认方式访问元素
}
if(prop === 'name'){
return obj.map(o=>o.name)
}
}
}
var PersonArray=[{
name:"Alice",age:23
},{
name:"Bob",age:45
},{
name:"Mike",age:27
}]
var p= new Proxy(PersonArray,AddSomeFunctionHandler)
p.name
// ["Alice", "Bob", "Mike"]
可撤销的代理
Proxy.revocable
Proxy拥有一个方法允许我们撤销之前存在的代理,还是按照上面的例子,我们定义一个可撤销的代理器
var pr=Proxy.revocable(PersonArray,AddSomeFunctionHandler)
var proxy=pr.proxy
proxy.name
// ["Alice", "Bob", "Mike"]
pr.revoke()
proxy.name
//Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
当该代理被撤销的时候,再去访问原来代理增加的行文属性将报错误出来。我们可以通过这些api实现的功能很多,比如防止元素的访问,设置私有属性等。灵活的代理设置,可以使得修改元素行为更加的方便快捷。Javascript之父Brendaneich在自己的blog中写过一些关于proxy的内容,另外还有一篇他在jsConf上的PPT,主题是《 Proxies are Awesome 》 ,只是时间略久远了一些。感兴趣的可以看一下。