什么是双向绑定

双向绑定是指,数据和视图的同步变动

为什么试用双向绑定

单向数据绑定,数据的流动方向繁多,不便追踪,然而当data数据变动时,就须要将之前的html代码去掉,把新的数据和模板插入到文档中。

双向数据绑定刚好解决了这个问题,当数据变动时,页面同时也会变动,只是不太容易跟踪数据源

如何实现双向绑定

vue是通过Object.defineProperty联合公布订阅者模式实现的

接下来咱们用代码来实现

  1. vue的模板文件
  <div id="app">      <input type="text" v-model="text" />      {{text}}  </div><script> new Vue({      data: {        text: "hello world",      },      el: "app",    }); </script>
  1. vue模板文件浏览器是不能辨认的,咱们须要将v-model 、{{}}等这样的语法转换成浏览器能够辨认的语法, 让text值能够回显的页面上

    文档碎片的作用:能够须要插入的节点,放到文档碎片上,最初一次性插入到文档中
// 创立Vue函数 初始化参数  function Vue(options) {      this.data = options.data;      this.el = options.el;        // nodeToFragment函数 返回浏览器能够辨认的dom元素      let dom = nodeToFragment(document.getElementById(this.el), this);      document.getElementById(this.el).appendChild(dom);    }    /**        获取生成的dom元素        node: app dom元素        vm vue对象    **/  function nodeToFragment(node, vm){      // 创立文档碎片      let fragment = document.createDocumentFragment();      while (node.firstChild) {          // 如果子元素,则编译其内容        compile(node.firstChild, vm);          // 将编译完的内容插入到模板碎片中,每插入一个元素,node对应的删除相应的元素(appendChild的作用)        fragment.appendChild(node.firstChild);      }      return fragment;  }  // 编译内容    function compile(node, vm) {      switch (node.nodeType) {        // 元素节点        case 1:          let atrr = node.attributes;          for (let i = 0; i < atrr.length; i++) {            if (atrr[i].nodeName === "v-model") {              let name = atrr[i].nodeValue;              // 监听键盘,每次扭转html中的值时,对应的data值也会变动              node.addEventListener("input", function (e) {                vm.data[name] = e.target.value;              });               // 将data的值,赋值给input              node.value = vm.data[name];            }          }          break;        // 文本节点        case 3:          // 正则匹配{{}}          let reg = /\{\{(.*)\}\}/;          if (reg.test(node.nodeValue)) {            let name = RegExp.$1;             // 将data的值,赋值给文本节点            node.nodeValue = vm.data[name];          }          break;        default:          break;      }    }
  1. 这个时候就要引入观察者订阅者模式,订阅者是指用到数据的中央,观察者是值察看数据的变动,而后告诉订阅者

订阅者:

 function Watcher(vm, node, name) {      Dep.target = this;      this.vm = vm;      this.node = node;      this.name = name;      this.update();      Dep.target = null;    }    Watcher.prototype = {      update() {         //  将data的值,更新视图        this.node.nodeValue = this.vm.data[this.name];      },    };

用dep寄存多个订阅者:

    function Dep() {      this.subs = [];    }    Dep.prototype = {      // 寄存订阅者,每个sub都是一个订阅者      addSub(sub) {        this.subs.push(sub);      },      // 触发订阅者      notify() {        this.subs.forEach((item) => {          item.update();        });      },    };

观察者:用到data数据时,触发get办法,将观察者存到dep中,当给data赋值时,触发set办法,从而触发dep的notify办法,更新所有用到这个数据的视图

   // 观察者,进行察看数据变动    function observe(obj) {      for (const key in obj) {        defaultReactive(obj, key, obj[key]);      }    }    // 用Object.defineProperty监听每个data值的变动     function defaultReactive(obj, key, val) {      // 每个属性都有一个dep      let dep = new Dep();      Object.defineProperty(obj, key, {        get: function () {          if (Dep.target) {            dep.addSub(Dep.target);          }          return val;        },        set: function (newVal) {          if (newVal == val) {            return;          }          val = newVal;          // 数据一旦变动,就要告诉扭转试图,及要通知订阅者          dep.notify();        },      });    }
  1. 当观察者和订阅者曾经实现后,咱们须要批改一些模板编译的内容,文本{{text}}用到了data值,即是一个订阅者

      let reg = /\{\{(.*)\}\}/;  if (reg.test(node.nodeValue)) {  let name = RegExp.$1;  // node.nodeValue = vm.data[name];  new Watcher(vm, node, name);  }

观察者一开始就要初始化的

 function Vue(options) {     this.data = options.data;     this.el = options.el;     observe(this.data);     let dom = nodeToFragment(document.getElementById(this.el), this);     document.getElementById(this.el).appendChild(dom); }