Vue源码分析之模板解析

模板解析

基本流程

1、将el的所有子节点取出,添加到一个新建的文档fragment对象中

function MVVM (options) {
    this.$options = options
    var data = this._data = this.$options.data
    var me = this

    // 创建一个编译对象
    this.$compile = new Compile(options.el || document.body, this)
}

function Compile (el, vm) {
    this.$vm = vm
    this.$el = this.isElement(el) ? el : document.querySelector(el)

    if (this.$el) {
        this.$fragment = this.node2fragment(this.$el)  //取出el元素中所有的子节点保存到一个fragment对象中
        this.init()
        this.$el.appendChild(this.$fragment)
    }
}

Compile.prototype = {
    node2fragment: function (el) {
        var fragment = document.createDocumentFragment(),
        child = this
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    },
}

2、对fragment中的所有层次子节点递归进行编译解析处理

function Compile (el, vm) {
    this.$vm = vm
    this.$el = this.isElement(el) ? el : document.querySelector(el)

    if (this.$el) {
        this.$fragment = this.node2fragment(this.$el)
        this.init()  //编译fragment中所有层次的节点
        this.$el.appendChild(this.$fragment)
    }
}

Compile.prototype = {
    init: function () {
        this.compileElement(this.$fragment)
    },

    compileElement: function (el) {
        var childNodes = el.childNodes,
        me = this;

        [].slice.call(childNodes).forEach(function (node) {
            var text = node.textContent,
            reg = /\{\{(.*)\}\}/;

            if (me.isElementNode(node)) {
                me.compile(node)
            } else if (me.isTextNode(node) && reg.test(node)) {
                me.compileText(node, RegExp.$1)
            }

            if (node.childNodes && node.childNodes.length) {  // 如果当前节点还有子节点,递归调用实现所有层次节点的编译
                me.compileElement(node)
            }
        })
    },
}

3、对表达式文本节点进行解析

Compile.prototype = {
    init: function () {
        this.compileElement(this.$fragment)
    },

    compileElement: function (el) {
        var childNodes = el.childNodes,
        me = this;

        [].slice.call(childNodes).forEach(function (node) {
            var text = node.textContent,
            reg = /\{\{(.*)\}\}/;   //  小括号匹配花括号里面的内容,例如{{class.no}}匹配class.no,在正则中可以用RegExp.$1取出

            if (me.isElementNode(node)) {
                me.compile(node)
            } else if (me.isTextNode(node) && reg.test(node)) {  // 判断节点是否是一个大括号格式的文本节点
                me.compileText(node, RegExp.$1)  // 编译大括号表达式文本节点
            }

            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node)
            }
        })
    },

    isElementNode: function (node) {
        return node.nodeType === 1
    },

    isTextNode: function (node) {
        return node.nodeType === 3
    },

    compileText: function (node, exp) {
        updateFn && updateFn(node, this.getVMVal(exp))
    },

    getVMVal: function (exp) {
        var val = this.$vm
        exp = exp.split('.')
        exp.forEach(function (k, v) {
            val = val[k]
        })
        return val
    },

    updateFn: function (node, val) {
        node.textContent = typeof val === 'undefined' ? '' : val
    }
}

4、对元素节点的一般指令属性进行解析

function Compile (el, vm) {
    this.$vm = vm
    this.$el = this.isElement(el) ? el : document.querySelector(el)

    if (this.$el) {
        this.$fragment = this.node2fragment(this.$el)
        this.init()  //编译fragment中所有层次的节点
        this.$el.appendChild(this.$fragment)
    }
}

Compile.prototype = {
    init: function () {
        this.compileElement(this.$fragment)
    },

    compileElement: function (el) {
        var childNodes = el.childNodes,
        me = this;

        [].slice.call(childNodes).forEach(function (node) {
            var text = node.textContent,
            reg = /\{\{(.*)\}\}/; 

            if (me.isElementNode(node)) {
                me.compile(node)  // 解析指令
            } else if (me.isTextNode(node) && reg.test(node)) {
                me.compileText(node, RegExp.$1)
            }

            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node)
            }
        })
    },

    compile: function (node) {
        var attrs = node.attributes,
        me = this;
        [].slice.call(attrs).forEach(function (attr) {
            var attrName = attr.name
            if (me.isDrection(attrName)) {
                var exp = attrName.value, 
                dir = attrName.slice(2); 
                if (me.isEventDirection(dir)) {
                    eventHandler(node, me.$vm, dir, exp)
                } else {
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp) //解析普通指令
                }
                node.removeAttribute(attrName)
            }
        })
    },
}

var compileUtil = {
    text: function (node, vm, exp) { this.bind(node, vm, exp, 'text') },
    html: function (node, vm, exp) { this.bind(node, vm, exp, 'html') },
    class: function (node, vm, exp) { this.bind(node, vm, exp, 'class') },
    bind: function (node, vm, exp, dir) {
        var updateFn = updater[dir+'Updater']
        updateFn && updateFn(node, this.getVMVal(vm, exp))
    },

    updateFn: function (node, val) {
        node.textContent = typeof val === 'undefined' ? '' : val
    },

    getVMVal: function (vm, exp) {
        var val = this.$vm
        exp = exp.split('.')
        exp.forEach(function (k, v) {
            val = val[k]
        })
        return val
    },
}

var updater = {
    textUpdater: function (node, val) {
        node.textContent = typeof val === 'undefined' ? '' : val;
    },
    htmlUpdater: function (node, val) {
        node.innerHTML = typeof val === 'undefined' ? '' : val;
    },
    classUpdater: function (node, val) {
        var className = node.className
        node.className = className ? className + ' ' + val : val
    },
    textUpdater: function (node, val) {
        node.textContent = typeof val === 'undefined' ? '' : val;
    },
}

5、事件指令解析

function Compile (el, vm) {
    this.$vm = vm
    this.$el = this.isElement(el) ? el : document.querySelector(el)

    if (this.$el) {
        this.$fragment = this.node2fragment(this.$el)
        this.init()  //编译fragment中所有层次的节点
        this.$el.appendChild(this.$fragment)
    }
}

Compile.prototype = {
    init: function () {
        this.compileElement(this.$fragment)
    },

    compileElement: function (el) {
        var childNodes = el.childNodes,
        me = this;

        [].slice.call(childNodes).forEach(function (node) {
            var text = node.textContent,
            reg = /\{\{(.*)\}\}/;

            if (me.isElementNode(node)) {
                me.compile(node)  // 解析指令
            } else if (me.isTextNode(node) && reg.test(node)) { 
                me.compileText(node, RegExp.$1)
            }

            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node)
            }
        })
    },

    compile: function (node) {
        var attrs = node.attributes,  // 得到标签的所有属性
        me = this;
        [].slice.call(attrs).forEach(function (attr) {  // 遍历所有属性
            var attrName = attr.name  // 得到属性名:v-on:click
            if (me.isDrection(attrName)) {  // 判断是否是指令属性
                var exp = attrName.value,  // 得到属性值-表达式:show
                dir = attrName.slice(2);  // 得到指令名:on-click
                if (me.isEventDirection(dir)) {  // 判断是否是事件指令
                    eventHandler(node, me.$vm, dir, exp)  // 解析事件指令
                }
                node.removeAttribute(attrName)  // 移除指令属性
            }
        })
    },

    isDrection: function (attr) {
        return attr.indexOf('v-') == 0
    },

    isEventDirection: function (attrName) {
        return attrName.indexOf('on') == 0
    },

    eventHandler: function (node, vm, dir, exp) {
        var eventType = dir.split(':')[1],  // 得到事件类型/名:click
        fn = vm.$options.methods && vm.$options.methods[exp];  // 从methods中得到表达式所对应的函数(事件回调函数)

        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false)  // 给节点绑定指定事件名和回调函数(强制绑定this为vm)的DOM事件监听
        }
    },
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值