简单实现Vue功能及原理总结

项目仓库:https://gitee.com/aeipyuan/vue_imitation.git

Vue类

获取数据并对各个工具类进行调度,通Object.defineProptert实现vm[key]=vm.$data[key]这一代理功能和computed、methods代理调用

/* 调度 */
class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        let methods = options.methods;
        if (this.$el) {
            /* 监听器 */
            new Observer(this.$data);
            /* computed */
            for (let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get: () => {
                        /* 执行该函数并返回值 */
                        return computed[key].call(this);
                    }
                })
            }
            /* 方法 */
            for (let key in methods) {
                Object.defineProperty(this, key, {
                    get() {
                        /* 返回该函数,不执行 */
                        return methods[key];
                    }
                })
            }
            /* 代理 */
            this.proxyVm(this.$data);
            /* 编译 */
            new Compile(this, this.$el);
        }
    }
    /* 代理 */
    proxyVm(data) {
        for (let key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set(newVal) {
                    data[key] = newVal;
                }
            })
        }
    }
}

Compile类

1.获取Vue实例对应的元素,然后将其所有子元素存入文档碎片
文档碎片作用:减少各元素改变时页面渲染次数,防止页面重排次数过多

    /* 转为文档碎片 */
    node2fragment(node) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        /* while循环将nodez全部加入到fragment */
        while (firstChild = node.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment;
    }

2.根据元素的nodeType来区别文本和元素,进行分类编译

    /* 判断是否为element */
    isElement(node) { return node.nodeType === 1; }
    /* 编译 */
    compile(node) {
        /* 获取子元素并遍历 */
        let childNodes = node.childNodes;
        childNodes.forEach(child => {
            /* 根据元素类型做不同处理 */
            if (this.isElement(child)) {
                this.compileElement(child);
                /* 编译子元素 */
                this.compile(child);
            } else {
                this.compileText(child);
            }
        })
    }

3.对于element,运用析构赋值,对属性进行拆分,然后在编译工具类执行操作

/* 编译元素 */
compileElement(node) {
    /* 取出属性 */
    let attributes = node.attributes;
    /* 对属性进行遍历 */
    [...attributes].forEach(attr => {
        let { name, value: valStr } = attr;/* v-on:click  showAge */
        /* 筛选'v-'开头的 */
        if (name.startsWith('v-')) {
            /* 删除'v-'取出类型 */
            name = name.slice(2);/* on:click */
            let [type, event] = name.split(':');/* on click */
            /* 调用编译工具执行对应操作 */
            CompileUtil[type](node, valStr, this.vm, event);
            /* CompileUtil.on(node,value,vm,click) */
        }
    })
}

4.对于文本,利用正则判断是否具有双括号,传入工具类处理

  /* 编译文本 */
  compileText(node) {
      let content = node.textContent;/* 名字:{{student.name}} */
      if (/\{\{(.+?)\}\}/.test(content)) {
          CompileUtil.text(node, content, this.vm);
      }
  }

CompileUtil对象

getVal、getTextVal、setVal等用于参数设置和修改,model、on、html、text调用参数处理,将回来的结果传给render的对应函数modelRender、htmlRender、textRender进行视图更新。

/* 编译工具 */
CompileUtil = {
    /* 根据valStr和vm得到数据 */
    getVal(vm, valStr) {
        /* 获取属性名数组 */
        let valArr = valStr.split('.'); /* ["student", "name"] */
        /* reduce结合数组获取数据 */
        return valArr.reduce((data, item) => {
            return data[item];/* 先获取vm.$data[student]传回,再获取student[name] */
        }, vm.$data);
    },
    /* 处理文本得到数据 */
    getTextVal(vm, valStr) {/* 名字:{{student.name}} */
        return valStr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            /* 将括号的值取出替换 */
            return this.getVal(vm, args[1]);
        })
    },
    /* 改变数据 */
    setVal(vm, valStr, value) {
        let valArr = valStr.split('.');
        valArr.reduce((data, item, index, arr) => {
            //修改数据
            if (index == arr.length - 1) {
                data[item] = value;
            }
            return data[item];
        }, vm.$data);
    },
    model(node, valStr, vm) {/* node student.name vm */
        /* 获取数据 */
        let value = this.getVal(vm, valStr);
        /* 调用render里的函数更新视图 */
        let rend = this.render['modelRender'];
        rend(node, value);
        /* 添加订阅者随时改变视图 */
        new Watcher(vm, valStr, (newVal) => {
            node.textContent = newVal
            rend(node, newVal);
        })
        /* 绑定input事件 */
        node.oninput = (e) => {
            this.setVal(vm, valStr, e.target.value);
        }
    },
    on(node, valStr, vm, event) {/* node showAge vm click*/
        node.addEventListener(event, (e) => {
            vm[valStr].call(vm, e);
        });
    },
    html(node, valStr, vm) {/* node msg vm */
        let value = this.getVal(vm, valStr);
        let rend = this.render['htmlRender'];
        rend(node, value);
        new Watcher(vm, valStr, newVal => {
            rend(node, newVal);
        })
    },
    text(node, valStr, vm) {/* node 名字:{{student.name}} vm*/
        let rend = this.render['textRender']; /* 名字:11 */
        let value = valStr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            /* 对每个数据添加订阅者 */
            new Watcher(vm, args[1], newVal => {
                rend(node, this.getTextVal(vm, valStr));
            })
            return this.getVal(vm, args[1]);
        })
        rend(node, value);
    },
    render: {
        modelRender(node, value) { node.value = value; },
        htmlRender(node, value) { node.innerHTML = value; },
        textRender(node, value) { node.textContent = value; }
    }
}

Observer监听器

利用Object.defineProperty对vm.$data进行监听,每个值设立一个属于自己的订阅器,改变时将通知所有订阅者

/* 监听器 */
class Observer {
    constructor(data) {
        this.observer(data);
    }
    /* 遍历data设置监听 */
    observer(obj) {
        if (obj && typeof obj == 'object') {
            /* 获取obj所有子属性 */
            let keys = Object.keys(obj);
            /* 遍历子属性,全部设置监听 */
            keys.forEach(key => {
                this.defineRactive(obj, key, obj[key]);
            })
        }
    }
    /* 监听 */
    defineRactive(obj, key, val) {
        /* 若子对象则继续遍历 */
        this.observer(val);
        /* 给每个值设置一个订阅器 */
        let dep = new Dep();
        /* 监听改变函数 */
        Object.defineProperty(obj, key, {
            get() {
                /* 判断是否有新订阅者 */
                Dep.newSub && dep.addSub();
                return val;
            },
            set: (newVal) => {
                /* 相等则没必要更新 */
                if (val !== newVal) {
                    /* 更新val已有的属性的数据,对新加的属性进行监听 */
                    if (typeof val == 'object') {/* 对象或数组 */
                        /* 更新内部内容 */
                        this.updateObj(val, newVal);
                    } else {
                        val = newVal;
                    }
                    /* 发布 */
                    dep.notify();
                }
            }
        })
    }
    /* 对象更新设置子数据更新 */
    updateObj(obj1, obj2) {
        if (obj1 instanceof Object || obj1 instanceof Array) {
            for (let key in obj1) {
                if (obj1[key] instanceof Object || obj1[key] instanceof Array) {
                    this.updateObj(obj1[key], obj2[key]);
                } else {
                    obj1[key] = obj2[key];
                }
            }
        } else {
            obj1 = obj2;
        }
    }
}

Dep订阅器

设立订阅数组subs存储订阅者,addSub向数组插入新的订阅者Dep.newSub(在Observer的get处调用),notify通知订阅者数组所有成员更新数据

/* 订阅器 */
class Dep {
    constructor() {
        /* 初始化订阅者数组 */
        this.subs = [];
    }
    /* 增加订阅者 */
    addSub() {
        this.subs.push(Dep.newSub);
    }
    /* 发布 */
    notify() {
        /* 通知所有订阅者更新数据 */
        this.subs.forEach(sub => {
            sub.update();
        })
    }
}

Watcher订阅者

构造时获取属性值触发get,从而调用Dep的addSub,将自己加入到对应的subs数组中,update用于更新数据并将值传给回调函数

/* 订阅者 */
class Watcher {
    constructor(vm, prop, cb) {
        this.vm = vm;
        this.prop = prop;
        this.cb = cb;
        /* 加入订阅者数组 */
        this.join();
    }
    /* 加入订阅者数组 */
    join() {
        /* 将自己设置为订阅器的新订阅者 */
        Dep.newSub = this;
        /* 读取一次数据触发get,从而间接触发addSub */
        this.oldVal = CompileUtil.getVal(this.vm, this.prop);
        /* 重置,否则会重复添加 */
        Dep.newSub = null;
    }
    /* 更新 */
    update() {
        /* 对比新旧值决定是否更新 */
        let newVal = CompileUtil.getVal(this.vm, this.prop);
        if (this.oldVal !== newVal) {
            this.oldVal = newVal;
            /* 运行回调函数 */
            this.cb(newVal);
        }
    }
}
posted @ 2020-03-31 22:03  aeipyuan  阅读(454)  评论(0编辑  收藏  举报