【Vue原理】Render - 源碼版 之 主要 Render

寫文章不容易,點個讚唄兄弟


專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧html

【Vue原理】Render - 源碼版 之 主要 Render node

compile 咱們已經講了九篇的內容了,終於走到了 render,今天就來給本身記錄下渲染三部曲的第二部,render,咦,render 內容很少的,就兩篇文章哈哈哈數組

噔噔噔噔函數

render 的做用你們應該清楚學習

就是 執行 compile 生成的 render函數,而後獲得返回的 vnode 節點this

好比如今存在這個簡單的模板spa

公衆號

通過 compile 以後,解析成了對應的 render 函數,以下prototype

function render() {    

    with(this) {        

        return _c('div', {            

            attrs: {                

                "data": 111

            }
        },
        [_v(111)])
    }
}

看着這個莫名其妙的 render 函數,裏面都是些什麼東西?3d

不怕,主要是出現了兩個函數,咱們要探索的就是這兩個東西code

_c , _v

這個兩個函數的做用,都是建立 Vnode,可是建立的過程不同

而且 render 函數執行的時候,會綁定上 模板對應的實例 爲上下文對象

模板是屬於哪一個實例的,就綁定哪一個實例

render.call(實例)

再經過 with 的做用

調用 _c 和 _v 就至關於 vm._c 和 vm._v


什麼是 vm._v

如今就來看看 vm._v 是哪裏來的

function installRenderHelpers(target) {
    target._v = createTextVNode;
}

installRenderHelpers(Vue.prototype);

由上面可知,每一個Vue 實例都會繼承有 _v 這個方法,因此能夠經過 vm._v 直接調用

再來看看 _v 對應的 createTextVNode 的做用是什麼

建立文本節點!!

看下源碼

function createTextVNode(val) {    

    return new VNode(        

        undefined, undefined,        

        undefined, String(val)

    )
}

好比這個模板

公衆號

{{data}} 雖然是字符串,可是也要做爲一個子節點存在,因此就當作是 文本節點

而 data 的值是 111

而後 上面的模板就會獲得這樣的 Vnode 結構以下

公衆號

什麼是 vm._c

_c 是一個大頭,render 的重中之重,先來看看他是怎麼來的

function initRender(vm) {
    vm._c = function(a, b, c, d) {        

        return createElement(vm, a, b, c, d);

    };
}

Vue.prototype._init = function(options) {
    initRender(this)
}

在實例初始化的時候,就會給實例綁定上 _c 方法

因此,vm 能夠直接調用到 _c

看了上面的源碼,看到 _c 內部調用了 createElement

那就來看看createElement 的源碼吧

我的已經簡化得很是簡單,以爲不偏離咱們的主題就能夠

function createElement(
    context, tag, data, children

) {    

    return _createElement(

        context, tag, data, children
    )
}



function _createElement(

    context, tag, data, children

) {    



    var vnode;    



    if (若是tag是正常html標籤) {

        vnode = new VNode(
            tag, data, children,
            undefined, undefined,
            context
        );
    }
    .....若是tag是組件名,就特殊處理 ,處理流程已經省略



    if (Array.isArray(vnode))

        return vnode    

    else {      

        // ...動態綁定 style ,class,代碼已經省略

        return vnode
    }
}

你一看就能夠看到,createElement 主要就是調用了 new VNode,固然了,render 就是爲了建立 vnode 的嘛

你在前面也看到了 render 函數,有傳了不少參數給 _c,以下,_c 再把這些參數傳給構造函數 VNode

_c('div',
    {            

        attrs: {"data": 111}

    },
    [_v(111)]
)

上面這些參數都會傳給 Vnode,並保存在建立的 Vnode 中

function VNode(
    tag, data, children, text

) {    

    this.tag = tag;

    this.data = data;    

    this.children = children;

    this.text = text;
}

而後獲得這麼一個 Vnode

{    

    tag:"div",    

    data:{        

        attrs: {"data": 111}

    },    

    children:[{        

        tag:undefined,        

        data:undefined,        

        text:111

    }]
}

說到這裏,已經能很清楚 render 內部是如何建立Vnode 了

可是這裏只是其中一種小小的簡單 render

要是項目中的render,數據是不少,很複雜的

而咱們主要要把握的是主要流程就能夠了

不過,還有必要記錄其餘 render,那就是遍歷


遍歷相關

看下面這個 template

公衆號

解析成下面的render

function render() {    

    with(this) {        

        return _c('div',

            _l(2,function(item, index) {                

                return _c('span')

            })
        )
    }
}

看到一個 _l, 他一定就是遍歷生成 Vnode 的幕後黑手了

一樣的,_l 和 _v 在同一個地方 installRenderHelpers 註冊的

function installRenderHelpers(target) {    
    target._l = renderList;
}

不客氣地搜出 renderList 源碼出來

先跳到後面的分析啊,源碼有點長了,雖然很簡單

function renderList(val, _render) {    



    var ret, i, l, keys, key;    



    // 遍歷數組

    if ( Array.isArray(val) ) {

        ret = new Array(val.length);  

    

        // 調用傳入的函數,把值傳入,數組保存結果
        for (i = 0, l = val.length; i < l; i++) {
            ret[i] = _render(val[i], i);
        }
    }    



    // 遍歷數字

    else if (typeof val === 'number') {

        ret = new Array(val);        



        // 調用傳入的函數,把值傳入,數組保存結果

        for (i = 0; i < val; i++) {
            ret[i] = _render(i + 1, i);
        }
    }    



    // 遍歷對象

    else if (typeof val =="object") {

        keys = Object.keys(val);
        ret = new Array(keys.length);        



        // 調用傳入的函數,把值傳入,數組保存結果

        for (i = 0, l = keys.length; i < l; i++) {
            key = keys[i];
            ret[i] = _render(val[key], key, i);
        }
    }    



    // 返回 vnode 數組
    return ret
}

看到 renderList 接收兩個參數,val 和 render,而 _l 調用的時候,也就是傳入的這兩個參數,好比下面

_l(2,function(item, index) {                
    return _c('span')
})

val 就是 2,_render 就是上面的函數

1 遍歷的數據 val

遍歷的數據分爲三種類型,一種是對象,一種是數字,一種是數組

2 單個 vnode 渲染回調 _render

重要是這個回調

一、renderList 每次遍歷都會執行回調,並把的每一項 item 和 index 都傳入 回調中

二、回調執行完畢,會返回 vnode

三、使用數組保存 vnode,而後 遍歷完畢就返回 數組

因而能夠看上面的 render 函數 ,傳入了 數字2,和 建立 span 的回調

_l(2,function(item, index) {    

    return _c('span')

})

_l 執行完畢,內部遍歷兩次,最後返回 兩個 span vnode 的數組,而後傳給外層的 _c ,做爲 vnode.children 保存

render 執行完畢,獲得這樣的 vnode

{    

    tag:"div",    

    data:undefined,    

    children:[{        

        tag:"span",        

        data:undefined

    },{        

        tag:"span",        

        data:undefined

    }]
}

都灰常簡單啊,沒寫以前,我還以爲內容應該挺多的,寫完發現還能夠

固然還有其餘的 render ,可是我都已經在其餘文章中有詳細的記錄了,能夠直接點鏈接觀看

好比要模板含有 filter

Filters - 源碼版

好比要模板含有 普通 slot
Slot - 源碼版之普通插槽

好比要模板含有 做用域 slot

Slot - 源碼版之做用域插槽

如今咱們來解決一個問題

render 何時開始執行?

能夠參考另外一篇文章:
從模板到DOM的簡要流程


總結

每一個模板通過 compile 都會生成一個 render 函數

render 做爲 渲染三部曲的第二部,主要做用就是 執行 render,生成 Vnode

把 template 上綁定的數據,都保存到 vnode 中

而後,生成 Vnode,就是爲了給 渲染三部曲的 第三部 Diff 提供源動力

從而完成 DOM 掛載

公衆號

相關文章
相關標籤/搜索