美文网首页
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