ES6 Deep Proxy
ES6中有一个新特性,叫做Proxy。可以用来定义基本操作的自定义行为,如属性查找、赋值、枚举、函数调用。
基本用法
Proxy的基本用法还是挺简单的,比如说用它实现一个 getter / setter。
1const foo = new Proxy({}, { 2 get: function(obj, prop) { 3 // 返回属性值,也可以做其它操作 4 return obj[prop]; 5 }, 6 set: function(obj, prop, value) { 7 obj[prop] = value 8 // 返回值代表是否赋值成功 9 // 如果返回false,在strict模式下,会抛异常。因此有时候即使阻断了赋值操作,也不一定要返回false。 10 return true; 11 } 12})
上面就是一个很简单的getter/setter实现了。
设置只读
如果想要设置对象只读,那么我们只需要将proxy中的setter方法设置成空函数即可。
1function protect (tp) { 2 return new Proxy(tp, { 3 get: function(obj, prop) { 4 // 返回属性值,也可以做其它操作 5 return obj[prop]; 6 }, 7 set: function(obj, prop, value) { 8 return true; 9 } 10 }) 11} 12 13const toProtect = { hello: 1, complex: { nested: 2 } } 14const protected = protect(toProtect) 15console.log(protected.hello) // => 1 16protected.hello = 2 17console.log(protected.hello) // => 1
从上面的代码中可以看出,在使用 Proxy 劫持 set 方法之后,给属性hello
赋值已经失去了作用。
但是上面的代码能否保护toProtect
下的所有属性呢?答案是不行的。
1const toProtect = { hello: 1, complex: { nested: 2 } } 2const protected = protect(toProtect) 3console.log(protected.complex.nested) // => 2 4protected.complex.nested = 3 5console.log(protected.complex.nested) // => 3
可以看出,protect方法只保护了对象的第一级属性,没能保护到嵌套的属性。
递归的第一种实现
要实现递归保护(深保护),思路是很简单的,在getter中也返回Proxy即可。
1function protect (tp) { 2 return new Proxy(tp, { 3 get: function(obj, prop) { 4 // 返回属性值,也可以做其它操作 5 if (typeof obj[prop] === 'object' && obj[prop] !== null) { 6 return protect(obj[prop]) 7 } 8 return obj[prop] 9 }, 10 set: function(obj, prop, value) { 11 return true; 12 } 13 }) 14} 15 16const foo = protect({hello: 1, world: {foo: 'bar'}, k: null, v: [null]}) 17 18console.log(foo.world.foo) // => 'bar' 19foo.world.foo = 1 20console.log(foo.world.foo) // => 'bar'
这种方法可实现基本的递归保护。
但是它是有问题的,因为每一次访问都会创建新的Proxy。所以长期使用下来的时候,性能会大幅下降。
递归的第二种实现
要避免反复创建Proxy,建立一个Map缓存结果即可。
因为每个Object都是一个引用,可直接拿来做key。
1const protectCache = new Map() 2function protect (tp) { 3 return new Proxy(tp, { 4 get: function(obj, prop) { 5 // 返回属性值,也可以做其它操作 6 const value = obj[prop] 7 if (protectCache.has(value)) { 8 return protectCache.get(value) 9 } 10 if (typeof value === 'object' && value !== null) { 11 const protected = protect(obj[prop]) 12 protectCache.set(value, protected) 13 return protected 14 } 15 return obj[prop] 16 }, 17 set: function(obj, prop, value) { 18 return true; 19 } 20 }) 21}
如上所示,protectCache
中缓存了Proxy的结果,下次访问时直接返回。这样就解决掉性能问题了。
至此,Proxy的深度处理完成。