vue 中render執行流程梳理

用了多年vue 今天對自己瞭解的render 做一個梳理

一、使用template模板

先從vue 初始化開始:
衆所周知項目的main.js中定義了
var app = new Vue({})這vue初始化操作

其實他會執行到

vue函數定義

這個方法中的_init函數,在這個方法執行一些列的初始化後,判斷$options是否定義el,如果定義調用
vm.$mount(vm.$options.el)函數,這個函數其實是在entry-runtime-with-compiler.js中定義的,if (!options.render) {}判斷是否定義了render函數,如果未定義再判斷是否定義template,如果定義直接調用‘compileToFunctions’函數將template編譯成爲一個匿名函數,這裏先借一個簡單案例,打個斷點測試下執行流程

import Vue from 'vue'

/**
 * 使用 template 方式
 */
var app = new Vue({
  el:'#admin',
  template:'<ul :class="bindCls" class="list" v-if="isShow">'+
  '<li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li></ul>',
  data(){
   return {
     bindCls:'a',
     isShow:true,
     data:['A','B','C','D']
   }
  },
  methods:{
    clickItem(index){
      console.log(index);
    }
  }
})

image.png

最後render匿名函數內容是這樣的

(function anonymous(
) {
with(this){return (isShow)?_c('ul',{staticClass:"list",class:bindCls},_l((data),function(item,index){return _c('li',{on:{"click":function($event){return clickItem(index)}}},[_v(_s(item)+":"+_s(index))])}),0):_e()}
})

看似一段簡短的render,其實生成這個render是一個相當複雜的過程

1、將template 轉換爲render 字符串

首先在compiler 文件中的index.js中
定義了createCompiler常量該值爲create-compiler.js裏的createCompilerCreator函數,同時將baseCompile函數傳入
而createCompilerCreator返回的是一個createCompiler函數而該createCompiler函數最後的返回值是一個
{ compile, compileToFunctions: createCompileToFunctionFn(compile) } 對象,


該對像中createCompileToFunctionFn其實是to-functiuon.js中定義的,目的就是把compile作爲參數傳入,進行一些列校驗合併等

再回過頭來調用compile實現編譯操作,取得最後compile編譯的結果,其實準確說是baseCompile編譯後的結果,因compile中其實是調用baseCompile進行模板編譯的(其中的通過parse 生成ast和generate生成code就不細說了,其實這個code中就包含了render),

createCompileToFunctionFn取得了編譯後的render並使用createFunction將其轉爲一個匿名函數,就是上面看見的樣子了


createCompilerCreator值返回到entry-runtime-with-compiler.js中的Vue.prototype.$mount函數中,同時將render掛載到了vu.$options上,再次調用runtime下index.js中的Vue.prototype.$mount函數

此時函數中又調用到了core/instance/lifecycle中的mountComponent函數;mountComponent函數在new Watcher中執行get(), 調用了updateComponent函數; 開始了執行vm._render()的操作,這個函數其實就定義在instance/render.js中;


這一連串的調用最終將template編譯爲render,又將其使用createElement編譯爲vdom,再使用__patch__初始化事件將其生成真實dom插入到頁面的body中

看文字很不直觀,這裏截幾個源碼圖來看看
判斷參數中是否存在render,不存在調用compileToFunction

調用createCompilerCreator將baseCompile作爲參數傳遞

將compile,createCompileToFunctionFn執行結果返回

createCompileToFunctionFn獲取執行compile後的render,將其緩存同時將其返回

2、render執行調用

好了現在就來看看render究竟是怎麼執行的,其實他的執行也挺簡單直接使用call進行調用,如
執行render

這個裏面的_renderProxy其實是在init.js中綁定的
初始proxy
proxy.js
其實就是在開發階段做一些校驗,再將vm 使用proxy代理一個handlers將其返回,而生產環境就是vm實例

vm.$createElement是上面一個initRender中初始化了,其實這個主要是用於我們手寫render用的

render.call(vm._renderProxy, vm.$createElement),中render就是上面說過的匿名函數

(function anonymous(
) {
with(this){return (isShow)?_c('ul',{staticClass:"list",class:bindCls},_l((data),function(item,index){return _c('li',{on:{"click":function($event){return clickItem(index)}}},[_v(_s(item)+":"+_s(index))])}),0):_e()}
})

他裏面的with作用更改作用域鏈,執行時會首先從局部去查找裏面的函數,如_c(),如果局部沒有就會到其綁定的作用域this中去找,在render.call時我們知道其實是將vm傳入了,而這個_c()就是上面initRender中定義的

定義_c(),
而其他的如_e(),_l()這些就是在render-helper中了
其他函數

而_c()又對應一個createElement函數這個就是真正的創建vdom的地方了,到此render 編譯template就完成了

二、手寫render

import Vue from 'vue'

var app = new Vue({
  el:'#admin',
  render(createElement){
    return createElement('div',{
      attrs:{
        id:'admin'
      }
    },this.message)
  },
  data(){
    return {
      message:'Hello vue'
    }
  }
})

而手寫render 其實是一樣的,只是在entry-runtime-with-compiler.js不會進行compileToFunctions編譯,而直接調用mount,而上面代碼中render裏的createElement是在render.call(vm._renderProxy, vm.$createElement)直接將createElement函數作爲參數傳入的,最後將該函數返回值返回給 Vue.prototype._update,然後將其使用vm.__patch__轉爲真實dom數插入到頁面中

三、自定義函數進行模擬

手動寫render


var vm = function(){}

 function createEl(a,b){
  console.log(a,b);
}

 vm.$createEl = (a,b) => createEl(a,b);


 render.call(vm,vm.$createEl);  // div1 div2


 function render(createEl){
  return createEl('div1','div2');
}


2、編譯 template爲render

   // vm構造函數
 var vm= function(){};
   // 創建vdom函數
 function createEl(a,b){
	  console.log(a,b);
 };
    // 模擬獲得編譯後的render 字符串
 var compiledRender = "with(this){return _c('div1','div2')}";
    // 將其轉換爲函數
 var render= new Function(compiledRender);

   // 爲vm定義_c函數
 vm._c = (a,b) => createEl(a,b);
// 爲vm定義$createEl 函數
 vm.$createEl = (a,b) => createEl(a,b);
	
  // 執行調用
  render.call(vm, vm.$createEl ); // 	div1 div2


到此把整個render 流程理了一遍,感謝閱讀有不正確處歡迎指正和探討,學習永不止步,探索從未停止。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章