美文网首页
vue源码分析

vue源码分析

作者: zxhnext | 来源:发表于2019-06-15 14:46 被阅读0次
    1. 首先来定义Vue函数,我们新建MVVM.js,如下所示:
    function Vue(options) {
      this.data = options.data;
      var data = this.data;
      observe(data, this);
      var id = options.el;
      var dom = new Compile(document.getElementById(id), this);
      // 编译完成后,将dom返回到app中
      document.getElementById(id).appendChild(dom);
    }
    
    1. 我们再来看Compile做了什么,以下是Compile.js
    function Compile(node, vm) {
      if (node) {
        this.$frag = this.nodeToFragment(node, vm);
        return this.$frag;
      }
    }
    Compile.prototype = {
      nodeToFragment: function (node, vm) {
        var self = this;
        var frag = document.createDocumentFragment(); // 创建一个dom片段
        var child;
    
        while (child = node.firstChild) {
          self.compileElement(child, vm);
          frag.append(child); // 将所有子节点添加到fragment中
        }
        return frag;
      },
      compileElement: function (node, vm) {
        var reg = /\{\{(.*)\}\}/;
        //节点类型为元素
        if (node.nodeType === 1) {
          var attr = node.attributes;
          // 解析属性
          for (var i = 0; i < attr.length; i++) {
            if (attr[i].nodeName == 'v-model') {
              var name = attr[i].nodeValue; // 获取v-model绑定的属性名(text)
              node.addEventListener('input', function (e) {
                // 给相应的data属性赋值,进而触发该属性的set方法
                //再批处理 渲染元素
                vm[name] = e.target.value; // 赋值
              });
              // node.value = vm[name]; // 将data的值赋给该node
              new Watcher(vm, node, name, 'value');
            }
          };
        }
        //节点类型为text
        if (node.nodeType === 3) {
          if (reg.test(node.nodeValue)) {
            var name = RegExp.$1; // 获取匹配到的字符串
            name = name.trim();
            // node.nodeValue = vm[name]; // 将data的值赋给该node
            new Watcher(vm, node, name, 'nodeValue');
          }
        }
      },
    }
    
    1. 再来看watcher
    let uid = 0;
    function Watcher(vm, node, name, type) {
        //new Watcher(vm, node, name, 'nodeValue');
        Dep.target = this;
        this.name = name; //text
        this.id = ++uid;
        this.node = node; //当前的节点
        this.vm = vm; //vm 
        this.type = type; //nodeValue
        this.update();
        Dep.target = null;
    }
    
    Watcher.prototype = {
        update: function () {
            this.get();
            if(!batcher){
                batcher = new Batcher();
            }
            batcher.push(this);
            //this.node[this.type] = this.value; // 订阅者执行相应操作
        },
        cb: function () {
            //最终实际虚拟dom处理的结果 只处理一次
            console.log("dom update");
            this.node[this.type] = this.value; // 订阅者执行相应操作
        },
        // 获取data的属性值
        get: function () {
            this.value = this.vm[this.name]; //触发相应属性的get
        }
    }
    
    1. Dep 发布订阅
    function Dep() {
      this.subs = [];
    }
    Dep.prototype = {
      addSub: function (sub) {
        this.subs.push(sub);
      },
      notify: function () {
        this.subs.forEach(function (sub) {
          sub.update();
        })
      }
    }
    
    1. 观察者模式
    function defineReactive(vm, key, val) {
          var dep = new Dep();
          Object.defineProperty(vm, key, {
            get: function () {
              if (Dep.target) {
                // JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
                dep.addSub(Dep.target);
              }
              return val;
            },
            set: function (newVal) {
              if (newVal === val) return;
              val = newVal;
              // 作为发布者发出通知
              dep.notify();
            }
          })
        }
    
        function observe(obj, vm) {
          Object.keys(obj).forEach(function (key) {
            defineReactive(vm, key, obj[key]);
          })
        }
    
    1. 批处理
    function Batcher() {
        this.reset();
    }
    
    /**
     * 批处理重置
     */
    Batcher.prototype.reset = function () {
        this.has = {};
        this.queue = [];
        this.waiting = false;
    };
    
    /**
     * 将事件添加到队列中
     * @param job {Watcher} watcher事件
     */
    Batcher.prototype.push = function (job) {
        let id = job.id;
        if (!this.has[id]) {
            console.log(batcher);
            this.queue.push(job);
            //设置元素的ID
            this.has[id] = true;
            if (!this.waiting) {
                this.waiting = true;
                if ("Promise" in window) {
                    Promise.resolve().then( ()=> {
                        this.flush();
                    })
                } else {
                    setTimeout(() => {
                        this.flush();
                    }, 0);
                }
            }
        }
    };
    
    /**
     * 执行并清空事件队列
     */
    Batcher.prototype.flush = function () {
        this.queue.forEach((job) => {
            job.cb();
        });
        this.reset();
    };
    
    1. vue模版如下:
    <!DOCTYPE html>
    
    <head></head>
    
    <body>
      <div id="app">
        <input type="text" id="a" v-model="text">
        {{text}}
      </div>
      <script>
        window.batcher = "";
      </script>
      <script src="src/Batcher.js"></script>
      <script src="src/Dep.js"></script>
      <script src="src/Observe.js"></script>
      <script src="src/Watcher.js"></script>
      <script src="src/Compile.js"></script>
      <script src="src/MVVM.js"></script>
      <script>
        var vm = new Vue({
          el: 'app',
          data: {
            text: 'hello world'
          }
        });
        for(var i=0;i<100;i++){
          vm["text"] = i;
        }
      </script>
    </body>
    
    </html>
    

    https://ustbhuangyi.github.io/vue-analysis/

    http://hcysun.me/vue-design/art/2vue-constructor.html

    ie6,7,8用san.js

    __defineGetter__
    __defineSetter__

    defineProperty不能监测数组,所以vue3用Proxy

    相关文章

      网友评论

          本文标题:vue源码分析

          本文链接:https://www.haomeiwen.com/subject/shaowqtx.html