运用 Proxy 完成简朴的 MVVM 模子

绑定完成的汗青

绑定的基础是 propertyChange 事宜。怎样得知 viewModel 成员值的转变一直是开辟 MVVM 框架的主要题目。主流框架的处置惩罚有一下三大类:

  1. 别的开辟一套 API。典范框架:Backbone.js

Backbone 有本身的 模子类鸠合类。如许做虽然框架开辟简朴运转效力也高,但开辟者不能不运用这套 API 操纵 viewModel,致使上手庞杂、代码烦琐。

  1. 脏搜检机制。典范框架:angularjs

特点是直接运用 JS 原生操纵对象的语法操纵 viewModel,开辟者上手简朴、代码简朴。但脏搜检机制随之带来的就是机能题目。这点在我别的的一篇博文 《Angular 1 深度剖析:脏数据搜检与 angular 机能优化》 有细致解说这里不另加赘述。

  1. 替换属性。典范框架:vuejs
    vuejs 把开辟者定义的 viewModel 对象(即 data 函数返回的对象)中一切的(除某些前缀开首的)成员替换为属性。如许既能够运用 JS 原生操纵对象的语法,又是主动触发 propertyChange 事宜,效力也高。但这类要领也有一些限定,后文会剖析。

Object.observe

Object.observe 是谷歌关于简化双向绑定机制的尝试,在 Chrome 49 中引入。然则由于机能等题目,并没有被其他各大浏览器及 ES 规范所接收。挣扎了一段时候后谷歌 Chrome 团队宣告收回 Object.observe 的发起,并在 Chrome 50 中完全删除了 Object.observe 完成。

Proxy

Proxy(代办)是 ES2015 到场的新特征,用于对某些基础操纵定义自定义行动,相似于其他语言中的面向切面编程。它的个中一个作用就是用于(部份)替换 Object.observe 以完成双向绑定。

比方有一个对象

let viewModel = {};

能够组织对应的代办类完成对 viewModel 的属性赋值操纵的监听:

viewModel = new Proxy(viewModel, {
  set(obj, prop, value) {
    if (obj[prop] !== value) {
      obj[prop] = value;
      console.log(`${prop} 属性被改成 ${value}`);
    }
    return true;
  }
});

这时候一切对 viewModel 的属性赋值的操纵都不会直接见效,而是将这个操纵转发给 Proxy 中注册的 set 要领,个中的参数 obj 是原始对象(注重不能直接用 a,不然还会触发代办函数,形成无穷递归),prop 是被赋值的属性名,value 是待赋的值。
假如有:

viewModel.test = 1;

这时候就会输出 test 属性被改成 1

用 Proxy 完成简朴的单向绑定。

有了 Proxy 就能够得知 viewModel 中属性的变动了,还须要更新页面上绑定此属性的元素。

简朴起见,我们用 this 示意 viewModel 本身,运用 this.XXX 就示意依靠 XXX 属性。有 DOM 以下:

  <div my-bind="'str1 + str2 = ' + (this.str1 + this.str2)"></div>
  <div my-bind="'num1 - num2 = ' + (this.num1 - this.num2)"></div>

起首要取得一切运用了单向绑定的元素:

const bindingElements = [...document.querySelectorAll('[my-bind]')];

猎取绑定表达式:

bindingElements.forEach(el => {
  const expression = el.getAttribute('my-bind');
});

由于取得的表达式是个字符串,须要组织一个函数去实行它,获得表达式的效果:

const expression = el.getAttribute('my-bind');
const result = new Function('"use strict";\nreturn ' + expression).call(viewModel);

代码中会动态建立一个函数,内容就是将字符串剖析实行后将其效果返回(相似 eval,但更平安)。将效果放到页面上就能够了:

el.textContent = result;

与上文的 viewModel 结合起来:

const bindingElements = [...document.querySelectorAll('[my-bind]')];

window.viewModel = new Proxy({}, { // 设置全局变量轻易调试
  set(obj, prop, value) {
    if (obj[prop] !== value) {
      obj[prop] = value;

      bindingElements.forEach(el => {
        const expression = el.getAttribute('my-bind');
        const result = new Function('"use strict";\nreturn ' + expression)
          .call(obj);
        el.textContent = result;
      });
    }
    return true;
  }
});

假如现实放在浏览器中运转的话,转变 viewModel 中属性的值就会触发页面的更新。

示例中写了轮回会更新一切绑定元素,比较好的体式格局是只更新对当前变动属性有依靠的元素。这时候就要剖析绑定表达式的属性依靠。
简朴起见能够运用正则表达式剖析属性依靠:

let match;
while (match = /this(?:\.(\w+))+/g.exec(expression)) {
  match[1] // 属性依靠
}

增加事宜绑定

事宜绑定即绑定原生事宜,在事宜触发时实行绑定表达式,表达式挪用 viewModel 中的某个回调函数。

click 事宜为例。依然是猎取一切绑定了 click 事宜的元素,并实行表达式(表达式的值被抛弃)。与单项绑定差别的是:实行表达式须要传入事宜的 event 参数。

[...document.querySelectorAll('[my-click]')].forEach(el => {
  const expression = el.getAttribute('my-click');
  const fn = new Function('$event', '"use strict";\n' + expression);
  el.addEventListener('click', event => {
    fn.call(viewModel, event);
  });
});

Function 对象的组织函数,前 n-1 个参数是天生的函数对象的参数名,末了一个是函数体。代码中组织了包括一个 $event 参数的函数,函数体就是直接实行绑定表达式。

双向绑定

双向绑定就是单项绑定和事宜绑定的结合体。绑定元素的 input 事宜来修正 viewModel 的属性,然后再单项绑定元素的 value 属性修正元素的值。

这里是一个较为完全的示例:http://sandbox.runjs.cn/show/…。完全的代码放在我的 GitHub 堆栈

运用 Proxy 完成双向绑定的优瑕玷

相较于 vuejs 的属性替换,Proxy 完成的绑定至少有以下三个长处:

  • 无需预先定义待绑定的属性。

vuejs 要做属性(getter, setter 要领)替换,起首须要晓得有哪些属性须要替换,如许致使必需预先定义须要替换的属性,也就是 vuejs 中的 data 要领。vuejs 中 data 要领必需定义完全一切绑定属性,不然对应绑定不能一般事情。
Vue 不能检测到对象属性的增加或删除Property or method "XXX" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
Proxy 不须要,由于它监听的是悉数对象。

  • 对数组相性优越。

虽然说数组里的要领能够替换(push、pop等),然则数组下标却不能替换为属性,致使必需搞出一个 set 要领用于对数组下标赋值

  • 更轻易调试的 viewModel 对象。

由于 vuejs 把对象中的一切成员悉数替换成了属性,假如想直接用 Chrome 的原生调试东西检察属性值,你不能不挨个去点属性背面的 (...):由于猎取属性的值现实上是实行了属性的 get 要领,实行一个要领可能会发生副作用,Chrome 把这个决定权留给开辟者。
Proxy 对象不须要。Proxyset 要领只是一层包装,Proxy 对象本身保护原始对象的值,天然也能够直接拿出原始值给开辟者看。检察一个 Proxy 对象,只须要睁开其内置属性 [[Target]] 即可看到原始对象的一切成员的值。你以至还能够看到包装原始对象的哪些 getset 函数——假如你感兴趣的话。

虽然说运用 Proxy 完成双向绑定的长处很明显,然则瑕玷也很明显:ProxyES2015 的特征,它没法被编译为 ES5,也没法 Polyfill。IE 天然全军尽没;其他各大浏览器完成的时候也较晚:Chrome 49、Safari 10。浏览器兼容性极大的限定了 Proxy 的运用。然则我置信,跟着时候的推移,基于 Proxy 的前端 MVVM 框架也会出如今开辟者面前。

    原文作者:CarterLi
    原文地址: https://segmentfault.com/a/1190000010433537
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞