Vue 源码分析

Vue 源码分析_第1张图片
vue.jpg

{{ num }}

一、处理模板

1、获取 template


var dom = document.querySelector(id);
var template = dom ? dom.outerHTML : '
' ...

{{ num }}

2、template 转 ast


var startRe = /^<(\w+)/
var bindRe = /^(:|v-bind:)/
var eventRe = /^(@|v-on:)/
var textRe = /\s*\{\{\s*([^\{\}]*)\s*\}\}\s*/

function parseHTML(ops){
    var html = ops.html
    while(html){
        var pos = html.indexOf('<')
        
        if(pos == 0){
            var start = html.match(startRe)
            if(start){
                // 开始标签
                continue
            }
            
            var end = html.match(endRe)
            if(end){
                // 结束标签
                continue
            }
        }
        
        if(pos >= 0){
            // 文本内容
            advance(pos)
        }
        
        if(pos < 0){
            // 处理完毕
            html = ''
        }
    }
    
    function advance(len){
        html = html.substr(len)
    }
}
...
var ast = {
    tag: 'div',
    attrs: {
        id: 'app'
    },
    bindClass: 'myclass',
    events: {
        click: 'show'
    },
    children: [...ast]
}

3、ast 转 render


function render(ast){
    var code = 'with(this){ return '
    code += renderCode(ast)
    return code +' }'
}

function renderCode(ast){
    if(ast.for && !ast.fored){
        return renderFor(ast)
    } else if(ast.if && !ast.ifed){
        return renderIf(ast)
    } else {
        var code = ''
        if(ast.type == 1){
            // dom 元素
            var data = renderData(ast)
            var children = renderChildren(ast.children)
            code += '_c("'+ ast.tag +'",'+ data +','+ children +')'
        } else if(ast.type == 2){
            // {{ ...}} 表达式
            code += renderText(ast.exp)
        } else {
            // 纯文本
            code += renderText(ast.text)
        }
        return code
    }
}
...
var render = 'with(this){ return _c("div",{bindClass:myclass,attrs:{"id":"app"},on:{click:show}},[_t("\n\t\t"),_c("p",{},[_t(_s(num))]),_t("\n\t")]) }'

4、render 转 vNode


function bind(fn, ctx){
    function bindFn(){
        arguments.length ? fn.apply(ctx, arguments) : fn.call(ctx)
    }
    return bindFn
}

this.$render = new Function(render)
var vNode = this.$render.call(this)
...
var vNode = {
    tag: 'div',
    attrs: {
        id: 'app'
    },
    bindClass: 'active',
    on: {
        click: bindFn()
    },
    elm: div#app.active,
    children: [...vNode]
}

二、初始化生命周期

1、构造函数


var did = 0

function Dep(){
    this.id = did++
    this.subs = [...Watcher]
}

Dep.prototype.notify = function(){
    var subs = this.subs
    for(var i=0, l=subs.length; i

2、初始化

var vid = 0

function V(ops){
    this.id = vid++
    this.$ops = ops
    this.$init()
}

V.prototype.$init = function(){
    initHook(this)
    initWorth(this)
    initRender(this)
}

function initWorth(ctx){
    initData(ctx, ops.data)
    initMethod(ctx, ops.methods)
    initFilter(ctx, ops.filters)
    initComputed(ctx, ops.computed)
    initWatch(ctx, ops.watch)
    callHook(ctx, 'created')
}

function initComputed(v, computed){
    for(var k in computed){
        defineComputed(v, k, computed[k])
    }
}

function defineComputed(obj, key, userDef){
    var getter = isFun(userDef) ? userDef : userDef.get
    var setter = isFun(userDef) ? noop : userDef.set
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: bind(getter, obj),
        set: bind(setter, obj)
    })
}

function initWatch(v, watch){
    for(var k in watch){
        watchHandle(v, k, watch[k])
    }
}

function watchHandle(obj, key, userDef){
    var fn = obj[userDef]
    var cb = isFun(fn) ? fn : noop
    new Watcher(obj, key, cb)
}

function initRender(ctx){
    ctx.$render = new Function(code)
    ctx.$watcher = new Watcher(ctx, ctx.$update, noop)
    removeNode(query(ops.el))
}

V.prototype.$update = function(){
    var node = this.$render.call(this)
    
    if(this.$node){
        patch(this.$node, node)
        callHook(this, 'updated')
    } else {
        initNode(node)
        callHook(this, 'mounted')
    }
    this.$node = node
}

三、渲染视图

1、初次渲染


function initNode(node, parent, ref){
    parent = parent || document.body
    foundElm(node, parent, ref)
}

function foundElm(node, parent, ref){
    var i = null
    if(node.tag){
        node.elm = makeElm(node.tag)
    } else if(node.annotate){
        node.elm = makeAnnotate(node.text)
    } else if(node.text){
        node.elm = makeText(node.text)
    }
    if(node.data){
        foundData(node)
    }
    // 挂载 dom
    makeNode(node.elm, parent, ref)
    if(isDef(i = node.data) && isDef(i = i.html)){
        node.elm.innerHTML = i
    } else if(isDef(i = node.data) && isDef(i = i.text)){
        node.elm.innerText = i
    } else if(node.children){
        for(var i=0, l=node.children.length; i

2、更新


function patchNode(a, b){
    var c = a.children
    var d = b.children
    var elm = b.elm = a.elm
    
    if(isUndef(b.text)){
        if(isDef(c) && isDef(d)){
            // diff 算法
            updateChildren(c, d, elm)
        } else if(isDef(c)){
            removeChildren(c, 0, c.length, elm)
        } else if(isDef(d)){
            addChildren(d, 0, d.length, elm)
        } else if(isDef(c.text)){
            elm.nodeValue = ''
        }
    } else if(a.text != b.text) {
        elm.nodeValue = b.text
    }
}

你可能感兴趣的:(Vue 源码分析)