- function MyVue (options = {}) {
-   //第一步:首先就是将实例化的对象给拿到,得到data对象
-   this.$options = options;
-   this._data = this.$options.data;
-   //第二步:数据劫持,将data对象中每一个属性都设置get/set方法
-   observe(this._data);
-   //第三步:数据代理,这里就是将_data的对象属性放到myVue实例中一份,实际的数据还是_data中的
-   for (let key in this._data) {
-     //这里的this代表当前myVue实例对象
-     Object.defineProperty(this, key, {
-       enumerable: true,
-       get () {
-         return this._data[key];
-       },
-       set (newVal) {
-         this._data[key] = newVal;
-       }
-     })
-   }
-   //第四步:compile模板,需要将el属性和当前myVue实例
-   compile(options.el, this)
- }
- function compile (el, vm) {
-   return new Compile(el, vm);
- }
- function Compile (el, vm) {
-   //将el代表的那个dom节点挂载到myVue实例中
-   vm.$el = document.querySelector(el);
-   //创建虚拟节点容器树
-   let fragment = document.createDocumentFragment();
-   //将el下所有的dom节点都放到容器树中,注意appendChild方法,这里是将将dom节点移动到容器树中啊,不是死循环!
-   while (child = vm.$el.firstChild) {
-     // console.log('count:' + vm.$el.childElementCount);
-     fragment.appendChild(child)
-   };
-   //遍历虚拟节点中的所有节点,将真实数据填充覆盖这种占位符{{}}
-   replace(fragment, vm);
-   //将虚拟节点树中内容渲染到页面中
-   vm.$el.appendChild(fragment);
- }
- function replace (n, vm) {
-   //遍历容器树中所有的节点,解析出{{}}里面的内容,然后将数据覆盖到节点中去
-   Array.from(n.childNodes).forEach(node => {
-     console.log('nodeType:' + node.nodeType);
-     let nodeText = node.textContent;
-     let reg = /\{\{(.*)\}\}/;
-     // 节点类型常用的有元素节点,属性节点和文本节点,值分别是1,2,3
-     //一定要弄清楚这三种节点,比如<p id="123">hello</p>,这个p标签整个的就是元素节点,nodeType==1
-     //id="123"可以看作是属性节点,nodeType==2
-     //hello 表示文本节点,nodeType==3
-     //因为占位符{{}}只在文本节点中,所以需要判断是否等于3
-     if (node.nodeType === 3 && reg.test(nodeText)) {
-       // RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2。。。
-       let arr = RegExp.$1.split(".");
-       let val = vm;
-       // 这个for循环就是取出这样的值:myVue[name][user]
-       arr.forEach(i => {
-         val = val[i];
-       })
-       // 创建Watcher,最主要的是传入的这个回调函数,会覆盖node节点中的占位符{{xxx}}
-       new Watcher(vm, RegExp.$1, function (newVal) {
-         node.textContent = nodeText.replace(reg, newVal);
-       })
-       // 把值覆盖到虚拟节点的占位符{{}}这里
-       node.textContent = nodeText.replace(reg, val);
-     }
-     //这里遍历到元素的节点,例如:<p id="xx">aaa</p></p>,<input type="text v-model=" username">
-     //然后需要取到其中的vue相关的指令,例如v-model="xxx",一般是以v-开头的
-     if (node.nodeType === 1) {
-       // console.log(node);
-       let nodeAttributes = node.attributes;
-       Array.from(nodeAttributes).forEach(attr => {
-         let name = attr.name;
-         let exp = attr.value;
-         // 找到v-model指令,data中的数据填充到input框中
-         if (name.indexOf("v-model") == 0) {
-           node.value = vm[exp];
-         }
-         // data中的数据变化,input中的数据也要跟着变化
-         new Watcher(vm, exp, function (newVal) {
-           node.value = newVal;
-         })
-         node.addEventListener("input", e => {
-           // 获取input输入框中的值
-           let inputValue = e.target.value;
-           //input中修改的值同步到到data中去,这里又会触发该属性的set方法,set方法中又会触发发布订阅模式,将所有的Watcher都调用一遍
-           vm[exp] = inputValue;
-         })
-       })
-     }
-     // 第一个遍历的节点是<div id="app">这一行后面的换行,nodeType等于3,但是没有占位符{{}},所以会进入到这里进行递归调用内部
-     //的每一个节点,直到找到文本节点而且占位符{{}}
-     if (node.childNodes) {
-       replace(node, vm);
-     }
-   })
- }
- //数据劫持操作
- function observe (data) {
-   // 如果data不是对象,就结束,不然递归调用会栈溢出的
-   if (typeof data !== 'object') return;
-   return new Observe(data);
- }
- function Observe (data) {
-   let dep = new Dep();
-   // 遍历data所有属性
-   for (let key in data) {
-     let val = data[key];
-     //初始化的时候, data中就有复杂对象的时候,例如data: { message:{a:{b:1}}}  ,就需要递归的遍历这个对象中每一个属性都添加get和set方法
-     observe(val);
-     Object.defineProperty(data, key, {
-       enumerable: true,
-       get () {
-         // 订阅
-         Dep.target && dep.addSub(Dep.target);
-         return val;
-       },
-       set (newVal) {
-         if (val === newVal) return;
-         val = newVal;
-         //当后续可能因为业务逻辑使得_data.message = {name: "小王"},设置对象类型的属性值,就需要递归的给对象中{name: "小王"}的每个属性也添加get和set方法
-         //否则name是没有get/set方法的
-         observe(val);
-         dep.notify();
-       }
-     })
-   }
- }
- // ===============================发布订阅===============================
- // 可以看做是公众号端
- function Dep () {
-   // 存放每个用户的容器
-   this.subs = [];
- }
- //对外提供的api之一,供用户订阅
- Dep.prototype.addSub = function (sub) {
-   this.subs.push(sub);
- }
- // 对外提供的api之二,遍历每个用户,给每个用法发信息
- Dep.prototype.notify = function () {
-   this.subs.forEach(sub => {
-     sub.update();
-   });
- }
- // 用户端
- function Watcher (vm, exp, fn) {
-   // 这个可以看作是用户的标志;注意,这个fn一般是一个回调函数
-   this.vm = vm;
-   this.exp = exp;
-   this.fn = fn;
-   Dep.target = this;
-   let val = vm;
-   let arr = exp.split(".");
-   arr.forEach(item => {
-     val = val[item];
-   })
-   Dep.target = null;
- }
- // 用户端提供的对外api,让公众号端使用
- Watcher.prototype.update = function () {
-   let val = this.vm;
-   let arr = this.exp.split(".");
-   arr.forEach(item => {
-     val = val[item];
-   })
-   this.fn(val);
- }