logo

鱼肚的博客

Don't Repeat Yourself

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的深度处理完成。