美文网首页前端常用技术点
Vue模板编译简单解析

Vue模板编译简单解析

作者: 赵小空 | 来源:发表于2019-10-24 14:15 被阅读0次

    compile的作用:

    • 解析指令(属性节点)与插值表达式(文本节点),并替换模板数据,初始化视图;
    • 将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图;

    什么是DocumentFragments?

    DocumentFragment,文档片段接口,表示一个没有父级文件的最小文档对象。它被作为一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式的 XML 片段。最大的区别是因为 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

    实现virtualNode,将挂载点内的所有节点存储到DocumentFragment中
    virtualNode(node) {
        let fragment = document.createDocumentFragment()
        // 把el中所有的子节点挨个添加到文档片段中
        let childNodes = node.childNodes
        // 由于childNodes是一个类数组,所以我们要把它转化成为一个数组,以使用forEach方法
        Array.from(childNodes).forEach(node => {
            // 把所有的字节点添加到fragment中
            fragment.appendChild(node)
        })
        return fragment
    }
    

    接下来我们要做的事情就是解析fragment里面的节点:compile(fragment)
    我们要递归遍历fragment里面的所有子节点,根据节点类型进行判断,如果是文本节点则按插值表达式进行解析,如果是属性节点则按指令进行解析。在解析属性节点的时候,我们还要进一步判断:是不是由v-开头的指令,或者是特殊字符,如@、:开头的指令。

    class Compile {
        constructor(el, vm) {
            this.el = typeof el === "string" ? document.querySelector(el) : el
            this.vm = vm
            // 解析模板内容
            if (this.el) {
            const fragment = this.virtualNode(this.el)
            this.compile(fragment)
            this.el.appendChild(fragment)
            }
        }
        // 解析fragment里面的节点
        compile(fragment) {
            let childNodes = fragment.childNodes
            this.toArray(childNodes).forEach(node => {
                // 如果是元素节点,则解析指令
                if (this.isElementNode(node)) {
                    this.compileElementNode(node)
                }
    
                // 如果是文本节点,则解析差值表达式
                if (this.isTextNode(node)) {
                    this.compileTextNode(node)
                }
    
                // 递归解析
                if (node.childNodes && node.childNodes.length > 0) {
                    this.compile(node)
                }
            })
        }
    }
    

    接下来我们要做的就只剩下解析指令,并通过watcher把解析后的结果通知给视图了。

    这里有两个问题大家需要注意一下:

    如果是复杂数据的情形,例如插值表达式:{{dad.son.name}}或者<p v-text='dad.son.name'></p>,我们拿到v-text的属性值是字符串dad.son.name,我们是无法通过vm.$data['dad.son.name']拿到数据的。因此,如果数据是复杂数据的情形,我们需要实现getVMData()setVMData()方法进行数据的获取与修改。
    在vue中,methods里面的方法里面的this是指向vue实例,因此,在我们通过v-on指令给节点绑定方法的时候,我们需要把该方法的this指向绑定为vue实例。

    let CompileUtils = {
        getVMData(vm, expr) {
            let data = vm.$data
            expr.split('.').forEach(key => {
                data = data[key]
            })
            return data
        },
        setVMData(vm, expr,value) {
            let data = vm.$data
            let arr = expr.split('.')
            arr.forEach((key,index) => {
                if(index < arr.length -1) {
                    data = data[key]
                } else {
                    data[key] = value
                }
            })
        },
        // 解析插值表达式
        mustache(node, vm) {
            let txt = node.textContent
            let reg = /\{\{(.+)\}\}/
            if (reg.test(txt)) {
                let expr = RegExp.$1
                node.textContent = txt.replace(reg, this.getVMData(vm, expr))
                new Watcher(vm, expr, newValue => {
                    node.textContent = txt.replace(reg, newValue)
                })
            }
        },
        // 解析v-text
        text(node, vm, expr) {
            node.textContent = this.getVMData(vm, expr)
            new Watcher(vm, expr, newValue => {
                node.textContent = newValue
            })
        },
        // 解析v-html
        html(node, vm, expr) {
            node.innerHTML = this.getVMData(vm, expr)
            new Watcher(vm, expr, newValue => {
                node.innerHTML = newValue
            })
        },
        // 解析v-model
        model(node, vm, expr) {
            let that = this
            node.value = this.getVMData(vm, expr)
            node.addEventListener('input', function () {
                // 下面这个写法不能深度改变数据
                // vm.$data[expr] = this.value
                that.setVMData(vm,expr,this.value)
            })
            new Watcher(vm, expr, newValue => {
                node.value = newValue
            })
        },
        // 解析v-on
        eventHandler(node, vm, eventType, expr) {
            // 处理methods里面的函数fn不存在的逻辑
            // 即使没有写fn,也不会影响项目继续运行
            let fn = vm.$methods && vm.$methods[expr]
            
            try {
                node.addEventListener(eventType, fn.bind(vm))
            } catch (error) {
                console.error('抛出这个异常表示你methods里面没有写方法\n', error)
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:Vue模板编译简单解析

        本文链接:https://www.haomeiwen.com/subject/nksrvctx.html