zoukankan      html  css  js  c++  java
  • 简单实现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);
            }
        }
    }
    
  • 相关阅读:
    解决Win8设置为快速启动后ubuntu不能访问win下磁盘的方法
    Why Not Specialize Function Templates?
    Build your first web service with PHP, JSON and MySql
    本地开发与linux服务器端的文件传输
    数据库设计中一对多的解决方法.
    C++ 模板练习摘要
    Understanding ASP.NET Validation Techniques
    我的sublime text 个人设置
    Eclipse 快捷键
    数字图像处理
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12638600.html
Copyright © 2011-2022 走看看