Vue 原理 - 实现 Vue 双绑不含虚拟 DOM

博客分类: 江河计划

Vue 原理 - 实现 Vue 双绑不含虚拟 DOM

算法

二分法搜索

Array.prototype.binarySearch = function (item) {
    this.sort((a, b) => {
        return a - b;
    });
    var low = 0,
        high = this.length - 1,
        mid = Math.ceil((low + high) / 2);
    while (low <= high) {
        if (this[mid] < item) {
            low = mid + 1;
        } else if (this[mid] > item) {
            high = mid - 1;
        } else {
            return mid;
        }
        mid = Math.ceil((low + high) / 2);
    }
    return -1;
}
var arr = [3, 4, 51, 2, 4, 2, 1, 23, 4, 5, 62, 123, 1, 231, 4, 123];
console.log(arr.binarySearch(1));

斐波那契

const fibonacci = (num: number) => {
    if (num === 1 || num === 2) {
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2);
}
console.log(fibonacci(6))

实现 Vue 双绑不含虚拟 DOM

Vue 实现双绑大概如下:

  1. 对数据进行递归的 Observe
  2. 实例化 dep,get 方法内放入 dep.addSubs,如果对应有 watcher 将 watcher 放入依赖绑定中
  3. set 方法中绑定 dep.notify,notify 调用与当前数据相关的所有 watcher
  4. 对模板进行递归解析,将 textnode 中的对应模板与数据进行隐射,初始化替换 node 节点内容之后,初始化一个 watcher,将改数据的 key 和 相关该 key 的回调传入 watcher
  5. watcher 实例化直接调用 watcher 的 get 方法,此时 get 方法中已经有一个方法,将该 watcher addSub 到依赖收集器中
  6. 初始化完毕
  7. 数据发生改变时,触发 set 方法,set 方法中触发 dep.notify,notify 触发与这个数据所有相关的 watcher.update 方法
  8. watcher.update 触发 run 方法,run 触发实例化 watcher 时与当前数据相关的模板回调。执行回调,发生页面改变
     // Vue 简单的对象
     const info = {
         el: document.querySelector('#karyn'),
         template: `<p></p><p></p><p></p>`,
         data() {
             return {
                 demo1: 'demo1',
                 demo2: 'demo2',
                 data: {
                     demo: 'demo'
                 }
             }
         }
     }
        
     // 依赖收集器
     class Dep {
         private subs: any[] = [];
         static target = null;
         addSub(sub) {
             // 将 watcher 添加进来
             this.subs.push(sub);
         }
         notify() {
             this.subs.map(item => {
                 // 执行 watcher 的 update 方法
                 item.update();
             })
         }
     }
        
     // 指令监听器
     class Watcher {
         private cb;     // 指令回调
         private info;   // 主函数信息
         private exp;    // 匹配的数据项
         private value;
         constructor(info, exp, cb: Function) {
             this.cb = cb;
             this.info = info;
             this.exp = exp;
             // 将 watcher 的 get 方法进行执行
             this.value = this.get();
         }
         update() {
             // 这里可以做个延迟,最后一次性 append dom
             this.run();
         }
         run() {
             // 深层递归
             var exps = this.exp.split('.');
             var value = this.info.data;
             while (exps.length) {
                 value = value[exps.shift()];
             }
             var oldVal = this.value;
             // 替换新旧的值执行 watcher 的回执
             if (value !== oldVal) {
                 this.value = value;
                 this.cb.call(this.info, value, oldVal);
             }
         }
         get() {
             Dep.target = this;
             // 深层递归
             var exps = this.exp.split('.');
             var value = this.info.data;
             while (exps.length) {
                 value = value[exps.shift()];
             }
             Dep.target = null;
             return value
         }
     }
        
     // 编译模板
     class Compile {
         private info = {};
         constructor(info) {
             this.info = info;
             // 编译模板
             this.compileElement(info.el);
         }
         compileElement(node) {
             var childNodes = node.childNodes;
             // 将每一个子节点拿出来查询
             [].slice.call(childNodes).map(item => {
                 var reg = /\{\{(.*)\}\}/;
                 var text = item.textContent;
                 // 如果是文本节点且有参数
                 if (this.isTextNode(item) && reg.test(text)) {
                     // 将模板中的数据进行替换
                     this.compileText(item, reg.exec(text)[1]);
                 }
                 // 如果还有子节点进行递归
                 if (item.childNodes && item.childNodes.length) {
                     this.compileElement(item);
                 }
             })
         }
         compileText(node, exp) {
             exp = exp.replace(/^\s*|\s*$/g, '');
             var exps = exp.split('.');
             var initText = this.info.data;
             // 将对应的值拿出来,如果是多层级可能会有问题
             while (exps.length) {
                 initText = initText[exps.shift()];
             }
             // 更新模板
             this.updateText(node, initText);
             // 实例化一个 watcher, 当 watcher 触发时执行回调
             new Watcher(this.info, exp, (value) => {
                 this.updateText(node, value);
             });
         }
         updateText(node, text) {
             node.nodeValue = text;
         }
         isTextNode(node) {
             return node.nodeType === 3;
         }
     }
        
     // 核心组件
     class Karyn {
         private $data: object = {};
         private data: object = {};
         private template: string = '';
         private el: any;
         constructor(info) {
             // Vue 中,这里是一个拷贝数据
             this.$data = info.data.call(this);
             // 将双板的数据放入 this.data 中
             this.data = info.data.call(this);
             // 缓存模板
             this.template = info.template;
             // 缓存跟节点
             this.el = info.el;
             // 先将节点拼接进去
             this.el.innerHTML = this.template;
             // 初始化双绑数据
             this.observe(this.data);
             // 对模板进行编译
             new Compile(this);
         }
         observe(data) {
             // 递归绑定
             if (!data || typeof data !== 'object') {
                 return;
             }
             // 由于 object 是引用类型,所以递归绑定也是同一个值
             Object.keys(data).map(item => {
                 // 真正绑定的方法
                 this.defineReactive(data, item, data[item]);
             });
         }
         defineReactive(data, key, val) {
             // 递归调用
             this.observe(val);
             // 初始化一个以来收集器
             var dep = new Dep();
             // 对一个数据进行绑定
             Object.defineProperty(data, key, {
                 get() {
                     // 第一次会默认调起
                     // 如果需要添加订阅者
                     if (Dep.target) {
                         // 添加订阅者
                         dep.addSub(Dep.target);
                     }
                     return val
                 },
                 set(newVal) {
                     if (val === newVal) {
                         return;
                     }
                     val = newVal;
                     // 赋值之后分发事件
                     dep.notify();
                 }
             })
         }
     }
        
     var karyn = new Karyn(info)
     setTimeout(() => {
         karyn.data.data.demo = '000';
         karyn.data.demo1 = '111';
         karyn.data.demo2 = '222';
     }, 2000)