深入浅出手撕简易VUE.JS和MVVM原理(二)

78 阅读2分钟

这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

一、compiler类的实现

书接上文,先来实现compile类,拿到真正的节点并且创建文档碎片,这里使用文档碎片是为了避免对节点的操作触发过多的重绘和回流,也就是做出一定的优化。

class compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el); // 元素节点直接给,字符串就去找
        this.vm = vm;
        const fragment = this.node2Fragment(this.el); // 为根节点el创建文档碎片对象
        this.compile(fragment); // 编译子节点
        this.el.appendChild(fragment); // 编译完成给真正的根节点去显示编译修改过的子节点们
    }
}
    node2Fragment (el) { // 创建文档碎片
        const f = document.createDocumentFragment();
        let firstChild;
        while(firstChild = el.firstChild) { // 每次while都重新给一个firstChild的值
            f.appendChild(firstChild); // 当前的firstChild也会被删除
        }
        return f;
    }
    isElementNode (node) { // 判断是否是元素节点
        return node.nodeType === 1;
    }

这里重点是compile方法,作用是分别对元素节点和文本节点进行编译,具体实现如下:

compile (fragment) {
        const childNodes = fragment.childNodes;
        [...childNodes].forEach((child) => {
            if (this.isElementNode(child)) { // 元素节点
                this.compileElment(child);
            } else { // 文本节点
                this.compileText(child);
            }
            if (child.childNodes && child.childNodes.length) { // 考虑到子节点可能有很多嵌套所以递归
                this.compile(child);
            }
        })
    }
    compileText (node) { // 文本节点处理
        const content = node.textContent; // 拿到对应的文本内容
        if ((/\{\{(.+?)\}\}/).test(content)) { // 文本内容是否匹配{{}}
            compileUtil['text'](node, content, this.vm); // 是就走text方法
        }
    }
    compileElment(node) { // 元素节点处理
        const attributes = node.attributes;
        [...attributes].forEach((attr) => { // 遍历元素们以键值对方式呈现
            const {name, value} = attr;
            if (this.isDirective(name)) { // v-text v-on:click 以V-开头不断分割得到操作名和紧跟着的vlaue
                const [, directive] = name.split('-'); // text on:click
                const [directiveName, eventName] = directive.split(':') // text on与click
                compileUtil[directiveName](node, value, this.vm, eventName); // 调用操作方法
                node.removeAttribute('v-' + directive)
            } else if (this.isEventName(name)) { // 以@开头
                const [, eventName] = name.split('@');
                compileUtil['on'](node, value, this.vm, eventName);
            }
        })
    }
    isEventName (attrName) { // 判断是否以@开头
        return attrName.startsWith('@')
    }
    isDirective (attrName) { // 判断是否以v-开头
        return attrName.startsWith('v-');
    }

这其中还涉及到了一个辅助对象compileUtil,辅助对象compileUtil对象里面有不同的方法,都是接受上面传来的参数们,这些方法的作用都是为了从data中拿到对应的值然后调用一个公共的赋值的方法来赋值更新,可以根据具体参数的情况进行增删,具体的实现留待下次细说。