Vue源码分析系列五: 响应式原理

栏目: 编程语言 · 发布时间: 5年前

  • vue.js采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty来触发各个属性的getter以及setter,在数据变动时发布消息给订阅者,并触发相应的监听回调。
  • 第一步
    1. 初始化Vue实例,将Vue实例上绑定 dep 属性(依赖收集)
    2. 调用Vue原型上的 _observe() 以及 _compile() 方法。、
  • 第二步
    1. 通过 _observe() 方法重写data对象的setter/getter方法,当我们对data对象的属性进行改变的时候,能够发布消息给订阅者(Watcher),触发监听函数(Watcher原型上的update()方法)
  • 第三步
    1. 通过 _compile() 方法解析模板字符串,即 v-model/v-click/v-html等
    2. 在解析模板的同时,往dep中添加相应的监听器。
    3. 在这里操作Vue实例中的 $data
  • 第四步
    1. 通过Watcher构造函数,收集需要监听的元素
    2. 在构造函数的原型上定义 update()方法,通过数据的改变从而改变视图。
  • 最后上代码(删除注释说明的话,核心代码150行不到)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      body {
        line-height: 120px;
        text-align: center;
        background: #fff;
        color: yellow;
      }
      h1 {
        background: red;
        display: inline-block;
        width: auto;
        padding: 12px 24px;
        margin: 0 auto;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <form>
        <input type="text" v-model="number" />
        <button type="button" v-click="increment">increment</button>
      </form>
      <h1 v-html="number"></h1>
    </div>

    <script>
      function Vue(options) {
        this._init(options);
      }

      Vue.prototype._init = function(options) {
        this.$options = options;
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        this.$methods = options.methods;

        // 依赖收集: 对dom进行编译解析(解析指令或模板语法)的时候收集依赖,在数据改变的时候(setter 中)进行更新。
        this.dep = {};
        
        this._observe(this.$data);
        this._compile(this.$el);
      };

      Vue.prototype._observe = function(obj) {
        var value;
        var _this = this;
        for (key in obj) {
          if (obj.hasOwnProperty(key)) {
            // 收集依赖,对所有属性都进行一个监听,在这里是 number
            // 在 dep 对象中添加一个 number 属性,其值是一个数组,数组中存放的是 Watcher 实例
            // 如果发现 number 发生了改变,就在 setter 中循环遍历notice,执行 Watcher 实例的 update 方法,统一更新 number
            _this.dep[key] = {
              notice: []
            };
            value = _this.$data[key]; // 将 value 赋值为最初是的 number 值

            var dep = _this.dep[key];
            Object.defineProperty(_this.$data, key, {
              get() {
                return value;
              },
              set(newVal) {
                value = newVal;
                dep.notice.forEach(item => {
                  // 这里的item就是Watcher实例,可以调用update()方法,通知更新
                  // 有几处用到了 number 属性,number.notice 就有几个 Watcher 实例
                  // notice: {
                  //   attr: "number",
                  //   el: Input,
                  //   name: "input",
                  //   value: "value",
                  //   vm: {...}
                  // }
                  item.update();
                });
              }
            });
          }
        }
      };

      Vue.prototype._compile = function(root) {
        // #app 根元素
        var nodes = root.children; // [form, h1]
        var _this = this;
        for (var i = 0, len = nodes.length; i < len; i++) {
          var node = nodes[i];
          if (node.children.length) {
            this._compile(node);
          }

          if (node.hasAttribute("v-click")) {
            // 下面这种方式,有点问题,当立即执行函数执行完后,attrVal泄露出去了
            // 导致解析 v-model 的时候,拿到的 attrVal 的值时 increment,而不是number
            // 要注意
            // 用这种方式也可以实现,那么在解析'v-model'的时候,需要将当前 (解析'v-model') if语句中var出来的attrVal传入到立即执行函数中去
            // 或者我们统一使用ES6中的 let 来声明 attrVal 变量。

            // var attrVal = node.getAttribute('v-click');
            // node.addEventListener('click', (function () {
            //   return _this.$methods[attrVal].bind(_this.$data);
            // })())

            // 这种方式就是当立即执行函数被销毁之后,var出来的attrVal不会泄露出来,污染别的变量,但是可以通过闭包可以访问得到。
            node.onclick = (function() {
              var attrVal = node.getAttribute("v-click");
              // 注意:methods方法里面用的 this,指的是 options 里面的 data,所以需要将方法的上下文半绑定为 data
              return _this.$methods[attrVal].bind(_this.$data);
            })();
          }

          if (node.hasAttribute("v-model") && node.tagName === "INPUT") {
            var attrVal = node.getAttribute("v-model");

            node.addEventListener(
              "input",
              (function(i) {
                // 因为 input 用到了 number,所以需要将 dep.number.notice 中添加 Watcher 实例,
                // 在 number 改变时,input 的值就需要改变
                _this.dep[attrVal].notice.push(
                  new Watcher("input", node, _this, attrVal, "value")
                );
                return function() {
                  // 当我们在 input 里面输入数据的时候,就会触发 number 的 setter 属性
                  _this.$data[attrVal] = nodes[i].value;
                };
              })(i)
            );
          }

          if (node.hasAttribute("v-html")) {
            var attrVal = node.getAttribute("v-html");
            _this.dep[attrVal].notice.push(
              new Watcher("h1", node, _this, attrVal, "innerHTML")
            );
          }
        }
      };

      class Watcher {
        constructor(name, el, vm, attr, value) {
          // name: input
          // el: current element
          // vm
          // attr: number
          // value: 元素的value (innerHTML, input.value)
          this.name = name;
          this.el = el;
          this.vm = vm;
          this.attr = attr;
          this.value = value;
          this.update();
        }
        update() {
          this.el[this.value] = this.vm.$data[this.attr];
        }
      }

      window.onload = function() {
        let vm = new Vue({
          el: "#app",
          data: {
            number: 0
          },
          methods: {
            increment() {
              this.number++;
            }
          }
        });
      };
    </script>
  </body>
</html>

复制代码

以上所述就是小编给大家介绍的《Vue源码分析系列五: 响应式原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

打造有吸引力的学习型社群

打造有吸引力的学习型社群

苏平、田士杰、吕守玉 / 机械工业出版社 / 45.00元

本书首先对社群的定位、准备和吸引粉丝方面等做了饶有趣味的介绍,从社群黏度的提升、社群知识的迭代与转化和社群的持续发展等多个角度入手,对学习型社群的运营手段、运营模式、运营规律和运营经验等进行了全方位剖析。从中国培训师沙龙这个公益社群近十年成功运营的经验中,为如何经营好学习型社群总结出了一套系统性的、具有实操价值的方法。并以此为基础,扩展到知识管理、团队管理、内容IP等领域,为有致于社团建设以及优质......一起来看看 《打造有吸引力的学习型社群》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具