ES5与ES6实现双向绑定(two-way-binding)的区别

669 阅读3分钟

数据绑定

单向绑定:将 Model 绑定到 View,当 Model 的数据被 JavaScript 代码更新时,View 就自动被更新。

双向绑定:在单向绑定的基础上将 View 绑定到 Model,也即 ViewModel 当中任意一方的更新都会引起对方的更新。

ES5:访问器实现数据劫持(Hijacking)

利用ES5对象的访问器属性 gettersetter ,当用户读取和写入某个特定的对象属性时,通过劫持实现 View => Model (通常在 getter 内)以及 Model => View (通常在 setter 内)的更新。

ES5 示例

视图部分(View)

<!--将视图部分中 foo 输入的值绑定到数据部分中的 input 变量上,并在 bar 的文本显示-->
<div>
    <input type="text" id="foo">
    <span id="bar"></span>
</div>

数据部分(Model)

  let input = {}
  Object.defineProperty(input, 'inputValue', {
      configurable: true,
      enumerable: true,
      get: function(){
          // View => Model
          return document.getElementById("foo").value;
      },
      set: function(newVal){
          // Model => View
          document.getElementById("foo").value = newVal;
          document.getElementById("bar").textContent = newVal;
      }
  })
  document.getElementById("foo").addEventListener('keyup', function(e){
      // 仅仅是个展示问题
      // get
      document.getElementById("bar").textContent = input.inputValue;
      // 或者 set 
      // input.inputValue = e.target.value

  })

ES6:代理器 Proxy 配合 Reflect

Proxy

Proxy 用于修改某些对目标对象的默认操作,可以理解为为在用户与目标对象之间假设一层拦截,or namely “代理”。用户对目标对象的访问,都必须先通过这层代理。因此通过 Proxy 的设置,我们可以对外加的访问进行过滤和改写。

一个 new Proxy(target, handler) 对象由两个对象组成: targethandler

  • target 就是被代理的对象
  • handler 就是一个包含了13种代理操作的对象

Reflect

Reflect 的核心就是将原有的Object的部分方法放到了Reflect上,比如:

Reflect.get(target, name, receiver)

该方法被用来读取一个对象的属性。

  • target: 目标对象
  • name: 是我们要读取的属性。
  • receiver(可选): 可以理解为上下文this对象。 以及
Reflect.set(target,name,value,receiver)
  • receiver: 可以理解为上下文this对象。如果我们在设置值的时候遇到setter函数,该参数就指向与setter中上下文this对象。
  • 返回值:该函数会返回一个Boolean的值,代表在目标对象上设置属性是否成功。

ES6 示例

视图部分(View)

<!--将视图部分中 foo 输入的值绑定到数据部分中的 input 变量上,并在 bar 的文本显示-->
<div>
    <input type="text" id="foo">
    <span id="bar"></span>
</div>

数据部分(Model)

// let input = { inputValue: "123", foo:"foo", bar:"bar" }
// target 就是第一个参数,它是否是一个具名对象其实无所谓
let inputProxy = new Proxy({}, {
    get: function (target, key, proxy) {
        console.log(target);
        return Reflect.get(target, key, proxy);
    },
    set: function (target, key, value, proxy) {
        document.getElementById("foo").value = value;
        document.getElementById("bar").textContent = value;
        return Reflect.set(target, key, value, proxy);

    }
}
);

document.getElementById("foo").addEventListener('keyup', function (e) {
    // 显然由于 Proxy getter 这里没有绑定到视图,所以用 Proxy setter 来绑定
    inputProxy.inputValue = e.target.value;
})

监听 input 的时候如果用户修改 inputProxy 的值,因为 inputProxy 代理了 input 这个对象 (as target), 所以可以调用 Reflcet 反射到原 input 对象。但反过来说,直接修改 input 的值就对 inputProxy 没有任何影响,是故 target 是叫 input 还是张三李四王五事实上都无所谓,具名对象更多可能是处于管理上的遍历吧我猜?

[TBA:ES6 Proxy 详解。以及 Reflect 详解 ]