Vue模板解析简化版

function Compile2(el, vm) {
    //保存vm
    this.$vm = vm
    //保存el
    this.$el = this.isElementNode(el) ? el : document.querySelector(el)
    //如果el元素存在
    if (this.$el) {
        //取出el的所有子节点放到虚拟内存中
        this.$fragment = this.node2Fragment(this.$el)
        // 编译fragment中的所有子节点
        this.init()
        //编译完成后初始化显示
        this.$el.appendChild(this.$fragment)

    }

}

Compile2.prototype = {
    //创建虚拟节点并将传来的el子节点都放进去
    node2Fragment(el) {
        let fragment = document.createDocumentFragment()
        let child = null
        while (child = el.firstChild) {
            //编译所有层次的子节点,添加到fragment中
            fragment.appendChild(child)
        }
        return fragment
    },
    init() {
        this.compileElement(this.$fragment)
    },
    //这个方法最终会编译处理el的所有层次的子节点,因为里面会递归调用
    compileElement(el) {
        //得到所有子节点
        let chileNodes = el.childNodes,
            //保存vm对象
            me = this
        Array.prototype.slice.call(chileNodes).forEach(node => {
            //保存节点的文本值,如果是文本节点的话,可以用来正则匹配
            let contentText = node.textContent
            //用于匹配{{}}的正则表达式,()里表示匹配到{{}}里的内容并保存起来
            let reg = /\{\{(.*)\}\}/
            //如果是元素节点,需要编译,解析元素节点里面的指令
            if (me.isElementNode(node)) {
                //如果是元素节点,就给元素节点里面的属性(是指令的情况下)绑定
                me.compile(node)
                //如果是文本节点,并且是大括号表达式,就需要处理表达式里的值渲染到fragment里
            } else if (me.isTextNode(node) && reg.test(contentText)) {
                //这里的$1就是刚刚正则匹配到()里保存的值,也就是{{}}里的内容
                //这个方法的最终结果就是,如果是{{}}的文本节点,就把文本节点替换为找到的值
                me.compileText(node, RegExp.$1)
            }
            //如果node还有子节点,继续递归调用方法
            if (node.childNodes) {
                me.compileElement(node)
            }

        })

    },
    //解析指令
    compile(node) {
        //这个方法是解析传来的节点的所有属性,如果是指令的话,就得到它的指令名和指令值,随后调用绑定指令的方法给绑定
        let attrs = node.attributes,
            me = this
        Array.prototype.slice.call(attrs).forEach(attr => {
            //得到属性名
            let name = attr.name // v-on:click
            //需要判断属性名是否是指令
            if (me.isDirective(name)) {
                //attr v-on:click='show'
                //name v-on:click
                //value show
                //得到指令名
                let dir = name.substring(2)// on:click
                //得到属性值
                let exp = attr.value
                //判断是事件指令还是其他指令
                //如果是事件指令
                if (me.isEventDirective(dir)) {
                    //如果是事件指令,就把指令名和指令值传入另一个方法中,由那个方法判断并完成绑定
                    //dir:指令名, exp: 属性值
                    //vm传进去是需要绑定到vm的methods的函数
                    compileUtil.eventHandler(node, dir, exp, me.$vm)
                }
                //如果是普通指令,判断这个指令是否存在后才能去处理
                else {
                    compileUtil[dir] && compileUtil[dir](node, this.$vm, exp)
                }

                //解析完了即处理完了事件指令之后,这个时候元素已经绑定上了对应的事件,因此可以将元素里的v-属性删除了
                //只需要传属性名就行
                node.removeAttribute(name)
            }

        })
    },
    compileText(node, exp) {
        //寻找{{}}里面的name对应vm._data里的值的逻辑
        compileUtil.text(node, this.$vm, exp)
    },
    isElementNode(el) {
        return el.nodeType === 1
    },
    isTextNode(el) {
        return el.nodeType === 3
    },
    //是否为指令属性
    isDirective(name) {
        return name.indexOf('v-') === 0
    },
    //判断是否是事件指令
    isEventDirective(name) {
        return name.indexOf('on') === 0
    }

}
//指令处理集合
let compileUtil = {
    //解析v-text || {{}}
    text(node, vm, exp) {
        this.bind(node, vm, exp, 'text')
    },
    //解析v-html
    html(node, vm, exp) {
        this.bind(node, vm, exp, 'html')
    },
    //解析v-class
    class(node, vm, exp) {
        this.bind(node, vm, exp, 'class')
    },
    //绑定指令
    eventHandler(node, dir, exp, vm) {
        //node元素是不可少的,绑定事件监听就是给node元素绑定的
        let eventFn = dir.split(':')[1],
            fn = vm.$option.methods && vm.$option.methods[exp]
        if (eventFn && fn) {
            //这里不能直接传fn函数,因为fn函数里面可能会有读取this(vm)的操作,如果直接传的话,这里的this不出意外就是Window了,因为绑定在了dom元素里面
            //使用bind函数可以强制绑定this为某个对象
            node.addEventListener(eventFn, fn.bind(vm))
        }
    },
    //解析指令的方法,实现初始化显示
    bind(node, vm, exp, dir) {
        let updateFn = updater[dir + 'Updater']
        //如果这个指令存在,这则用来更新节点
        updateFn && updateFn(node, this._getVMVal(vm, exp))

    },
    //找到vm里面对应的值
    _getVMVal(vm, exp) {
        let val = vm._data
        //这里可能是寻找vm.a.b.c,因此需要对vm进行遍历,直到找到最深处的值
        exp = exp.split('.')
        exp.forEach(key => {
            val = val[key]
        })
        return val
    }
}
//用于更新节点信息
let updater = {
    textUpdater(node, value) {
        //上面找到了值,这里更新fragment对应节点的值
        //节点的文本内容等于传来的值,如果为空这处理一下
        node.textContent = typeof value === 'undefined' ? '' : value
    },
    htmlUpdater(node, value) {
        node.innerHTML = typeof value === 'undefined' ? '' : value
    },
    classUpdater(node, value) {
        let className = node.className
        //这里需要判断原来的node里面有没有设置class\
        node.className = (className ? (className + ' ') : '') + value
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值