ES6 Deep Proxy

发布于: 2020-08-23作者: 鱼肚最后更新: 2020-11-24

ES6 Deep Proxy

ES6中有一个新特性,叫做Proxy。可以用来定义基本操作的自定义行为,如属性查找、赋值、枚举、函数调用。

基本用法

Proxy的基本用法还是挺简单的,比如说用它实现一个 getter / setter。

1
2
3
4
5
6
7
8
9
10
11
12
const foo = new Proxy({}, {
    get: function(obj, prop) {
    // 返回属性值,也可以做其它操作
    return obj[prop];
  },
  set: function(obj, prop, value) {
    obj[prop] = value
    // 返回值代表是否赋值成功
    // 如果返回false,在strict模式下,会抛异常。因此有时候即使阻断了赋值操作,也不一定要返回false。
    return true;
  }
})

上面就是一个很简单的getter/setter实现了。

设置只读

如果想要设置对象只读,那么我们只需要将proxy中的setter方法设置成空函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function protect (tp) {
  return new Proxy(tp, {
    get: function(obj, prop) {
      // 返回属性值,也可以做其它操作
      return obj[prop];
    },
    set: function(obj, prop, value) {
      return true;
    }
  })
}

const toProtect = { hello: 1, complex: { nested: 2 } } 
const protected = protect(toProtect)
console.log(protected.hello) // => 1
protected.hello = 2
console.log(protected.hello) // => 1

从上面的代码中可以看出,在使用 Proxy 劫持 set 方法之后,给属性hello赋值已经失去了作用。

但是上面的代码能否保护toProtect下的所有属性呢?答案是不行的。

1
2
3
4
5
const toProtect = { hello: 1, complex: { nested: 2 } } 
const protected = protect(toProtect)
console.log(protected.complex.nested) // => 2
protected.complex.nested = 3
console.log(protected.complex.nested) // => 3

可以看出,protect方法只保护了对象的第一级属性,没能保护到嵌套的属性。

递归的第一种实现

要实现递归保护(深保护),思路是很简单的,在getter中也返回Proxy即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function protect (tp) {
  return new Proxy(tp, {
    get: function(obj, prop) {
      // 返回属性值,也可以做其它操作
      if (typeof obj[prop] === 'object' && obj[prop] !== null) {
        return protect(obj[prop])
      }
      return obj[prop]
    },
    set: function(obj, prop, value) {
      return true;
    }
  })
}

const foo = protect({hello: 1, world: {foo: 'bar'}, k: null, v: [null]})

console.log(foo.world.foo) // => 'bar'
foo.world.foo = 1
console.log(foo.world.foo) // => 'bar'

这种方法可实现基本的递归保护。

但是它是有问题的,因为每一次访问都会创建新的Proxy。所以长期使用下来的时候,性能会大幅下降。

递归的第二种实现

要避免反复创建Proxy,建立一个Map缓存结果即可。

因为每个Object都是一个引用,可直接拿来做key。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const protectCache = new Map()
function protect (tp) {
  return new Proxy(tp, {
    get: function(obj, prop) {
      // 返回属性值,也可以做其它操作
      const value = obj[prop]
      if (protectCache.has(value)) {
        return protectCache.get(value)
      }
      if (typeof value === 'object' && value !== null) {
        const protected = protect(obj[prop])
        protectCache.set(value, protected)
        return protected
      }
      return obj[prop]
    },
    set: function(obj, prop, value) {
      return true;
    }
  })
}

如上所示,protectCache中缓存了Proxy的结果,下次访问时直接返回。这样就解决掉性能问题了。

至此,Proxy的深度处理完成。

关注我:
分享文章:

0条评论