demo在线地址: https://codesandbox.io/embed/like-vue-qs42n?fontsize=14&hidenavigation=1&theme=dark

分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | let vm = new Vue({         el: "#app",         data: {           msg: " 改变",           obj: {             title: "<strong>仿写vue</strong>",             o: {               name: "asdasdas"             }           }         },         methods: {           handle() {             console.log(this);             alert(123);             this.msg = "shijiangaibiancanshu1";           }         }       });
  | 
 
调用vue就是实例化一个Vue类的过程, vue的各个api继承自Vue类,option通过参数传入, 由于es5代码会比较啰嗦,采用es6方式, el是作用域的根节点,可以传入选择器或者element节点, data是响应的数据也是比较核心的部分,
可以看到首先判断元素绑定是否成功,除去数据代理先不讲,程序主要执行两个步骤,分别处理模版和数据:
- new Observer: 将整个数据data传入
 
- new Compile: 将根元素el传入
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | class Vue {   constructor(options) {     this.el = options.el;     this.data = options.data;     this.methods = options.methods;     if (this.el) {              new Observer(this.data);              this.proxy(this.data, this);              new Compile(this.el, this);     }   }
    proxy(data, vm) {           Object.keys(data).forEach(key => {       Object.defineProperty(vm, key, {         get() {           return data[key];         },         set(newVal) {           data[key] = newVal;         }       });     });   } }
  | 
 
一个最基本的MVVM框架需要实现这几个模块: 数据劫持(observer), 模版编译(compile), 依赖收集(dep), 发布订阅(watcher,又称观察者), 代码都是比较常用的那类,难点在于设计模式的精巧和抽象,下面的图片,需要好好的理解

右边部分我的理解大致是这样的: 在实例化时将数据传入类的内部,检测传参是否为对象,遍历传参对象的属性并递归嵌套对象,然后处理数据,关键api是es6的Object.defineProperty文档参考,简单理解就是自定义对象属性,并通过getter和setter这个钩子进行我们的数据观察,(大喊一声:这里先占一个坑!)
在每一次遍历数据,劫持前,实例化一个依赖收集器, 该收集器具有添加观察者(watcher)存储,和通知更新两个方法,
在劫持数据的getter时收集器执行添加watcher, 在数据发生setter时,收集器通知更新
watcher
watcher类主要功能接收一个回调,和获取对象数据的新旧值比对,在发生改变的时候,触发回调的执行
模版编译compile
模版编译是将根元素下的所有节点转换为文档片段createDocumentFragment提高性能,再插入根节点渲染,遍历文档片段下的每一个节点的nodeTye,区分是元素节点(如果是元素节点可能存在嵌套,递归处理)还是文本节点,拆分两种处理函数..
源码: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
   | class Compile {   constructor(el, vm) {     this.el = this.isElement(el) ? el : document.querySelector(el);     this.vm = vm;     let fragment = this.node2Fragment(this.el);     this.compile(fragment);     this.el.appendChild(fragment);   }
    isElement(el) {     return el.nodeType === 1;   }
    node2Fragment(el) {     let f = document.createDocumentFragment();     let firstChild;
      while ((firstChild = el.firstChild)) {       f.appendChild(firstChild);     }     return f;   }
    compile(fragment) {     let nodes = fragment.childNodes;     [...nodes].forEach(node => {       if (this.isElement(node)) {         this.compileElement(node);         this.compile(node);       } else {         this.compileText(node);       }     });   }
    compileElement(node) {     let attributes = node.attributes;     [...attributes].forEach(attr => {       let { name, value } = attr;       if (this.isDirective(name)) {         let [, directive] = name.split("-");         let [dirName, eventName] = directive.split(":");         // console.log(name, value, directive);         compileUtil[dirName](node, value, this.vm, eventName);         node.removeAttribute(`v-${directive}`);       } else if (this.isEvent(name)) {         let [, eventName] = name.split(":");         compileUtil["on"](node, value, this.vm, eventName);       }     });   }
    compileText(node) {     let textContent = node.textContent;     if (/\{\{(.+?)\}\}/.test(textContent)) {       compileUtil["text"](node, textContent, this.vm);     }   }
    isDirective(name) {     return name.startsWith("v-");   }
    isEvent(name) {     return name.startsWith("@");   } }
  | 
 
原创手打,留言支持一下吧~