MVVM原理(2):指令解析器Compile

一、构建Compile类
  • 大致过程就是获取el元素的内容,对内容进行编译后重新插入到页面中
1、获取el元素的内容
class Compile {
    constructor (el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm

        // 采用文档碎片编译el.innerHTML,减少对页面的频繁操作,防止重排重绘影响性能
        const fragment = this.nodeFragment(this.el)
        this.el.appendChild(fragment)
    }
    isElementNode (node) {
        return node.nodeType === 1
    }
    nodeFragment (el) {
        const f = document.createDocumentFragment()
        let firstChild

        while(firstChild = el.firstChild) {
            f.appendChild(firstChild)
        }

        return f
    }
}
  • while循环的判断条件中,每一次循环时判断语句都会执行,即firstChild = el.firstChild在每次while判断中都会执行一次
  • appendChild是剪切操作,因此每循环一次elfirstChild均会被剪切后添加到文档碎片f中,循环结束后el的子节点为空
2、编译
2.1 compile函数
class Compile {
	constructor (el, vm) {
		...
		this.compile(fragment)
		...
	}
	compile (fragment) {
        const child = fragment.childNodes;
        [...childNodes].forEach(child => {
             if(this.isElementNode(child)) {
                 // 处理元素节点
                 this.compileElement(child)
             } else {
                 // 处理文本节点
                 this.compileText(child)
             }

             // child中还有子节点继续遍历处理
             if(child.childNodes && child.childNodes.length) {
                 this.compile(child)
             }
        })
    }
}
2.2 compileElement函数
// 指令对应处理方法
const compileUtil = {
    text (node, expr, vm) {
        const value = this.getVal(expr, vm)  // 取值
        this.updater.textUpdater(node, value)  // 设置节点的内容
    },
    html (node, expr, vm) {
        const value = this.getVal(expr, vm)  // 取值
        this.updater.htmlUpdater(node, value)  // 设置节点的内容
    },
    model (node, expr, vm) {
		const value = this.getVal(expr, vm)
        this.updater.modelUpdater(node, value)
    },
    getVal (expr, vm) {
        return expr.split('.').reduce((data, curVal) => {
            return data[curVal]
        }, vm.$data)
    },
    updater: {
        textUpdater (node, value) {
            node.textContent = value
        }, 
        htmlUpdater (node, value) {
            node.innerHTML = value
        },
        modelUpdater (node, value) {
            node.value = value
        }
    }
}

class Compile {
	...
	compileElement (node) {
	    const attributes = node.attributes;   // 获取元素属性
	    [...attributes].forEach(attr => {
	        const {name, value} = attr
	        if(this.isDirective(name)) {   // 判断是否是指令
	            const [, directive] = name.split('-')   // derective:text html model on:click bind:xxx
	            const [dirName, eventName] = directive.split(':')  // dirName:text html model on bind
	            compileUtil[dirName](node, value, this.vm, eventName)   // 根据具体指令具体处理
	            node.removeAttribute('v-' + directive)  // 删除属性
	        }
	    });
	}
}
2.3 compileText函数
class Compile {
	...
	compileText (node) {
        const content = node.textContent
        // 匹配{{xxx}},.+?的意思是匹配除\n外的任意字符1个到多个,?是非贪婪匹配,碰到第一个}就停止
        if(/\{\{(.+?)\}\}/.test(content)) {  // 内容有{{}}形式,把所有内容进行处理
            compileUtil['text'](node, content, this.vm)
        }
    }
}

这里我们把compileUtil['text']修改一下:

const compileUtil = {
    text (node, expr, vm) {
        let value
        let reg = /\{\{(.+?)\}\}/g
        if(reg.test(expr)) {  // 处理文本
            value = expr.replace(reg, (...args) => {
                return this.getVal(args[1], vm)
            })
        } else {
            value = this.getVal(expr, vm)  // 处理v-text
        }
        this.updater.textUpdater(node, value)
    },
    ...
}
2.4 事件绑定
  • v-on: 添加compileUtilon处理方法即可
const compileUtil = {
	...
	on (node, expr, vm, eventName) {
        let fn = vm.$options.methods && vm.$options.methods[expr]
        node.addEventListener(eventName, fn.bind(vm), false)  // 方法中的this要指向vue实例
    },
	...
}
  • @:修改compileElement函数
class Compile {
	...
	compileElement (node) {
		...
        [...attributes].forEach(attr => {
            ...
            if(this.isDirective(name)) {   // 判断是否是v-指令
               ...
            } else if (this.isEventName(name)) {   // 处理@
                let [, eventName] = name.split('@')
                compileUtil['on'](node, value, this.vm, eventName)   // 根据具体指令具体处理
                node.removeAttribute('@' + eventName) 
            }
        });
	}
	...
	isEventName (attrName) {
        return attrName.startsWith('@')
    }
}
2.5 v-bind:
  • v-bind:添加compileUtilbind处理方法即可
const compileUtil = {
	...
	bind (node, expr, vm, attrName) {
        const value = this.getVal(expr, vm)
        this.updater.bindUpdater(node, attrName, value)
    },
    ...
    updater: {
        ...
        bindUpdater (node, attrName, value) {
            node.setAttribute(attrName, value)
        }
    }
}
  • ::修改compileElement函数
class Compile {
	...
	compileElement (node) {
		...
        [...attributes].forEach(attr => {
            ...
            if(this.isDirective(name)) {   // 判断是否是v-指令
               ...
            } else if (this.isAttrName(name)) {  // 处理:
                let [, AttrName] = name.split(':')
                compileUtil['bind'](node, value, this.vm, AttrName)
                node.removeAttribute(':' + AttrName)
            }
        });
	}
	...
	isAttrName (attrName) {
        return attrName.startsWith(':')
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值