引言:ES6规范里面新增了Proxy对象,在高级范畴的js编程或者底层脚本的编写有这极强的作用。 |
一、Proxy的基本用法
Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程"( meta programming),即对编程语言进行编程。
Proxy可以理解成在目标对象前架设一“拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理"”某些操作,可以译为“代理器”。
在介绍Proxy对象的基本用法之前,我们先来讲述一下JS中对象的机制。例如,我先简单的创建一个对象:var obj = {};,现在obj对象是没有属性的。假如我再写一条语句obj.id=1;,此时,虽然obj对象没有id这条属性,但是obj.id=1操作是调用了JS对象机制内部的set方法。
下面,给出Proxy对象定义的基本格式:var proxy = new Proxy(target , handler);
Proxy对象的所有用法都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成个Proxy实例,target参数表示所要拦截的目标对象的名称,hander参数是一个函数方法,用来定制拦截行为。
代码块1-1运用Proxy对象写了一个拦截对象对自己的属性进行赋值与取值的代理器。
/***@@ 代码块1-1 拦截器举例一 @@***/ 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
代码块1-1解释: 首先,定义obj对象为Proxy类。 Proxy第一个参数应该是一个对象名,表示要拦截的目标对象,本例为空,则指向源对象obj。 Proxy第二个参数是自定义的拦截方法。 本例是对JS对象机制的get与set进行重定义,其中target参数是目标对象名,key是属性名。 当obj.count=1;代码运行时,首先会调用obj对象的set函数机制,然后被Proxy拦截,输出setting count!。 当obj.count++;运行时,先get到count的值,再去set。
代码块1-1对一个空对象做了一层拦截,重定义了属性的读取(get)和设置(set)行为。Proxy 实际上重载(overlad)了点运算符,即用自己的定义覆盖了语言的盾始定义。
下面是另一个拦截读取属性行为的例子。见代码块1-2。
/***@@ 代码块1-2 拦截读取属性 @@***/ var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); proxy.time // 35 proxy.nane // 35 proxy.title // 35
上面的代码中,作为构造函数Proxy接受两个参数:第一个参数是所要代理的目标对象(上例中是一个空对象),即如果没有Proxy介人,操作原来要访问的就是这个对象;第二个参数是配置对象,对于每一个被代理的操作,需要提供一 个对应的处理函数,该函数将拦截对应的操作。比如,上面的代码中, 配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都将得到35。
注意,要使Proxy起作用,必须针对Proxy实例(上例中是proxy对象)进行操作,而不是针对目标对象(上例中是空对象)进行操作。
如果handler没有设置任何拦截,那就等同于直接通向原对象。见代码块1-3。
/***@@ 代码块1-3 @@***/ var target = {}; var handler = {}; var proxy = new Proxy(target, handler); proxy.a = 'b'; target.a // "b"
上面的代码中,handler 是一个空对象,没有任何拦藏效果,访问hndler就等同于访问target。
一个技巧是将 Proxy对象设置到object.proxy属性,从而可以在object对象上调用。
var object= { proxy: new Proxy(target, handler) }
二、Proxy示例的方法
1)get()
get方法用于拦截某个属性的读取操作。前面已经有一个例子, 下面是另一个拦截读取操作的例子。见代码块2-1-1。
/***@@ 代码块2-1-1 @@***/ var person = { name: "张三" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); proxy.name // "张三” proxy.age //抛出一个错误
2)set()
set方法用于拦截某个属性的赋值操作。
假定Person对象有个age属性,该属性应该是个不大于200的整数,那么可以使用Porxy对象保证age的属性值符合要求。见代码块2-2-1。
/***@@ 代码块2-2-1 @@***/ let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TyeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } //其他属性一律保存 obj[prop] = value; } }; let person = new Proxy({}, validator); person.age = 100; person.age // 100 person.age = 'young' //报错 person.age = 300 //报错
上面的代码中,由于设置了存值函数set,任何不符合要求的age属性赋值都会抛出一个错误。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新DOM。
有时,我们会在对象上设置内部属性,属性名的第一个字符使用 下画线开头,表示这些属性不应该被外部使用。结合get和set方法,就可以做到防止这些内部属性被外部读写。
/***@@ 代码块2-2-1 屏蔽下划线开头的属性 @@***/ var handler = { get (target, key) { invartant(key, 'get'); return target[key]; }, set (target, key, value) { invartant(key, 'set'); return true; } } functton invartant (key, action) { if (key[0] === '_') { throw new Error(`Invalid attempt to ${action} private "${key}" property`); } } var target = {}; var proxy = new Proxy(target, handler); proxy._prop //Error: Invalid attempt to get private " prop" property proxy._prop = 'c' // Error: Invalid attempt to set private " prop" property
上面的代码中,只要读写的属性名的第一个字符是下画线,一律抛出错误,从而达到禁止读/写内部属性的目的。
3)apply()
apply方法拦截函数的调用、call和apply操作。形式如下:
var handler = { apply (target, ctx, args) { return Reflect. apply(…arguments);} }
apply方法可以接受3个参数,分别是目标对象、目标对象的上下文对象( this )和目标对象的参数数组。下面是一个例子。
var target= function () { return 'I am the target'; }; var handler = apply: function () { return 'I am the proxy'; }; var p = new Proxy(target, handler); p() === 'I am the proxy'; // true
上面的代码中,变量p是Proxy的实例,当作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。
查看更多ES6教学文章:
参考文献
阮一峰 《ES6标准入门(第2版)》