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 流程理了一遍,感谢阅读有不正确处欢迎指正和探讨,学习永不止步,探索从未停止。

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