es6 proxy小注及应用

241 阅读3分钟

js代理(Proxy)的作用是对基础操作(取值,赋值,调用函数等)进行自定义。e.g. 你有一个对象obj = {},当对其属性赋值时obj.age = 5,你希望做一些验证比如age不能赋值为string。这时代理就可以帮上忙。

用法

const p = new Proxy(target, handler);

target:可以是任意对象,包括数组,函数,或者另一个代理

handler:属性是预定义函数的对象,它定义了这个代理的行为。这个对象内的函数叫做陷阱(traps),因为你一旦对代理进行操作,相关的陷阱就会被触发。

来看一个最基本的例子,如上赋值obj.age = 5

const obj = {};
const proxy = new Proxy(obj, {
    set(target, key, value, receiver) {
        if (key === 'age') {
            if (!Number.isInteger(value)) {
                return false;
                // 或者 throw new TypeError('only allow int');
            }
            target[key] = value;
            return true;
        }
    }
});

proxy.age = 5; // OK
proxy.age = 'lol'; // 隐式赋值失败,或抛错

set方法必须返回布尔值,表示赋值成功与否。返回false在严格模式下会抛错TypeError

set函数的四个参数:

target:你代理的真正对象,上面就是obj

key: 你访问的代理属性,如上age

value:给上面属性赋的值,如上5

receiver:被调用的对象,要么是代理,要么是继承代理的对象。一般情况下,如上,显而易见就是proxy,但是某些情况下代理并不是最开始被调用的对象。e.g. 假设你赋值p.age = 5,p不是代理,p自己也没有age属性,但是p的原型链上有个代理,所以当那个代理的set被调用时,receiver其实是p。


ps:赋值和返回true的的那句有些人喜欢写成

return Reflect.set(target, key, value, receiver);

上面这个基本等价于target[key] = value; 但是它返回设置成功与否,所以省了一步哈。

主要的陷阱函数

上面说了set,基本上所有对对象进行基本操作的方法都有相应的trap。这里只谈一下has(), get(), apply(), construct()

has

const obj = {_secret: 'hidden property', show: 'show'};
const proxy = new Proxy(obj, {
    has(target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
});

'_secret' in obj // false
'show' in obj // true

这个trap是给in用的(不是for in),Reflect.has()也会触发,上面例子基本上自我解释了,很容易懂。必须返回boolean。

get

var p = new Proxy(target, {
  get: function(target, property, receiver) {
  }
});

p.foo或p[bar]都会触发。可以返回任意值。

apply

var p = new Proxy(target, {
  apply: function(target, thisArg, argumentsList) {
  }
});

p(),p.call(this), p.apply(this)都可触发,可以返回任意值。

construct

var p = new Proxy(target, {
  construct: function(target, argumentsList, newTarget) {
  }
});

new一个实例的时候触发,必须返回一个对象。

newTarget:被调用的构造器,如上p

应用

实现一个简单的双向绑定

<body>
  <input type="text" id="model">
  <p id="word"></p>
</body>
<script>
  const model = document.getElementById("model")
  const word = document.getElementById("word")
  var obj= {};

  const newObj = new Proxy(obj, {
      set: function(target, key, value, receiver) {
        if (key === "text") {
          model.value = value;
          word.innerHTML = value;
        }
        return Reflect.set(target, key, value, receiver);
      }
    });

  // change ui will change local object
  model.addEventListener("keyup",function(e){
    newObj.text = e.target.value
  })

  // later on, if you want to programmaticly change local object, it will also reflect on UI
  newObj.text = 'something i like';
</script>

取得输入框和显示框元素,给输入框加监听,如果用户输入了,那么对代理的text属性赋值, 这是ui改js,如果后面用户想直接修改newObject,其改动也会反映在UI上,因为代理内部有给元素赋值。