Proxy是ES6的语法。对于ES6的箭头函数、变量的解构赋值,咱们都能耳熟能详,熟练使用。对于Proxy 这样的特性却不多用到,一方面考虑到兼容性的问题(babel已经解决了),另外一方面确实这些特性不那么容易使用,也就是使用场景很少。数组
Proxy主要是用来修改某些操做的默认行为,从这个角度来说,你能够把他理解成一个拦截器。想要访问对象,都要通过这层拦截。那么咱们就能够在这层拦截上作各类操做了。好比你设置一个对象的值的时候,对对象的值进行校验等。babel
Proxy 支持的拦截操做一共 13 种:app
经过proxy能够作什么呢?函数
1.用来拦截和监听对对象属性的各类操做,能够在各类操做以前作校验或者打日志等。这个很简单了。this
2.中断操做设计
目标对象不容许直接访问,必须经过代理访问,一旦访问结束,就收回代理权,不容许再次访问。代理
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
3.能够经过Proxy实现是对象的私有变量(真正的私有)日志
// filter参数做为一个过滤函数出入 function priavateProp(obj, filter){ const handler = { get(obj, prop) { if(!filter(prop)){ let val = Reflect.get(obj, prop) if(typeof val === 'function'){ val = val.bind(obj) } return val } }, set(obj, prop, val) { if(filter(prop)){ throw new Error(`cannot set property ${prop}`) } return Reflect.set(obj, prop, val) }, has(obj, prop) { return filter(prop) ? false : Reflect.has(obj, prop) }, ownKeys(obj) { return Reflect.ownKeys(obj).filter( prop => !filter(prop)) } } return new Proxy(obj, handler) } // 私有属性过滤器 // 规则:以 _ 为开头的属性都是私有属性 function filter(prop){ return prop.indexOf('_') === 0 } const o = { _private: 'private property', name: 'public name', say(){ // 内部访问私有属性 console.log(this._private) } } const p = priavateProp(o, filter) console.log(p) // Proxy p._private // undefined JSON.stringify(p) // "{"name":"public name"}" // 只能内部访问私有属性 p.say() // private property console.log('_private' in p) // false // 不能遍历到私有属性 Object.keys(p) // ["name", "say"] // 私有属性不能赋值 p._private = '000' // Uncaught Error: cannot set property _private
4.扩充操做符code
使用 Proxy 但是实现操做符的重载,但也只能对 in of delete new 这几个实现重载对象
咱们劫持 in 操做符来实现 Array.includes 检查值是否存在数组中
function arrIn(arr){ const handler = { has(arr, val) { return arr.includes(val) } } return new Proxy(arr, handler) } const arr = arrIn(['a', 'b', 'c']) 'a' in arr // true 1 in arr // false
5.追踪数组和对象的变更。听说下一版本的VUE就是经过这种方式来进行数据绑定的。
function trackChange(target, fn){ const handler = { set(target, key, value) { const oldVal = target[key] target[key] = value; fn(target, key, oldVal, value) }, deleteProperty(target, key) { const oldVal = target[key] delete target[key] fn(target, key, oldVal, undefined) return true; } } return new Proxy(target, handler) } const obj = trackChange({}, (target, key, oldVal, newVal) => { console.log(`obj.${key} value from ${oldVal} to ${newVal}`) }) obj.a = 'a111' // obj.a value from undefined to a111 obj.a = 'axxxxx' // obj.a value from a111 to axxxxx delete obj.b // obj.b value from undefined to undefined obj.c = 'c1' // obj.c value from undefined to c1 const arr = trackChange([1, 2, 3, 4, 5], (target, key, oldVal, newVal) => { let val = isNaN(parseInt(key)) ? `.${key}` : `[${key}]` const sum = arr.reduce( (p,n) => p + n) console.log(`arr${val} value from ${oldVal} to ${newVal}`) console.log(`sum [${arr}] is ${sum}`) }) arr[4] = 0 // arr[4] value from 5 to 0 // sum [1,2,3,4,0] is 10 delete arr[3] // arr[3] value from 4 to undefined // sum [1,2,3,,0] is 6 arr.length = 2 // arr.length value from 5 to 2 // sum [1,2] is 3
里面有些疑点在此披露下:
拿这个deleteProperty(target, propKey)来讲,我查了不少资料上说,若是这个方法抛出错误或者返回false,当前属性就没法被delete命令删除。(暂时未找到标准上是怎么解释的)
var handler = { deleteProperty (target, key) { console.log('delete '+ key) return true }, }; function invariant (key, action) { if (key[0] === '_') { return false; } } var target = { _prop: 'foo' }; var proxy = new Proxy(target, handler); delete proxy._prop console.log(proxy._prop) console.log(target._prop)
上面的代码返回true,并无删除。其实若是你在这个函数里面执行了删除操做,返回什么并不重要。也就是说这个函数返回值是true,或者false,并不能影响删除的结果。除非你抛出来错误,那这个代码就执行不了了,直接报错了。试验了defineProperty,也是这样的。这些拦截函数,返回结果是不重要的,重要的是你在拦截相应的操做的时候,你作了什么操做,好比你拦截的是删除属性,那你必需要有删除属性这个操做,你要是给这个属性赋值,那确定达不到效果。这一点要搞清楚。
可是若是代理的是个数组,那他们return的值就必须得是true。
var handler = { set(target,key,value,proxy){ var oldVal = target[key]; var newVal = value; target[key] = value console.log(`arr ${key} changed from ${oldVal} to ${newVal}`) }, deleteProperty (target, key) { console.log('delete '+ key) delete target[key]; }, }; var arr = new Proxy([1,2,3], handler); arr.push(2);//'set' on proxy: trap returned falsish for property '3' arr.pop();//'deleteProperty' on proxy: trap returned falsish for property '2'
这就是set和deleteProperty不返回结果,爆出来的问题。每一个函数中返回true,返回结果就是
arr 3 changed from undefined to 2 arr length changed from 4 to 4 delete 3 arr length changed from 4 to 3
因此无论怎么样,对于这类返回boolean类型的方法,咱们必定要记得返回值。
还有一种规范的写法,咱们通常都是经过Reflect进行拦截操做,而后将其结果返回。
var handler = { set(target,key,value,proxy){ var oldVal = target[key]; var newVal = value; console.log(`arr ${key} changed from ${oldVal} to ${newVal}`) return Reflect.set(target,key,value,proxy); }, deleteProperty (target, key) { console.log('delete '+ key) return Reflect.deleteProperty(target,key) }, }; var arr = new Proxy([1,2,3], handler); arr.push(2); arr.pop();
那什么是Reflect呢?
Reflect对象与Proxy对象同样,也是ES6为了操做对象而提供的新API。Reflect对象的设计目的有这样几个。
它拥有的API和Proxy一一对应。