vue双向绑定原理

Posted 啦啦啦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue双向绑定原理相关的知识,希望对你有一定的参考价值。

什么是双向绑定

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

为什么试用双向绑定

单向数据绑定,数据的流动方向单一,方便追踪,但是当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);
 }

以上是关于vue双向绑定原理的主要内容,如果未能解决你的问题,请参考以下文章

Vue2从入门到精通详解Vue数据双向绑定原理及手动实现双向绑定

剖析Vue原理&实现双向绑定MVVM

剖析Vue原理&实现双向绑定MVVM

VUE底层原理之数据双向绑定

vue的双向绑定原理及实现

vue的双向绑定原理及实现