欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue 中render执行流程梳理

程序员文章站 2022-03-10 09:57:01
...

用了多年vue 今天对自己了解的render 做一个梳理

一、使用template模板

先从vue 初始化开始:
众所周知项目的main.js中定义了
var app = new Vue({})这vue初始化操作

其实他会执行到

vue 中render执行流程梳理

这个方法中的_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);
    }
  }
})

vue 中render执行流程梳理

最后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中

看文字很不直观,这里截几个源码图来看看
vue 中render执行流程梳理

vue 中render执行流程梳理

vue 中render执行流程梳理

vue 中render执行流程梳理

2、render执行调用

好了现在就来看看render究竟是怎么执行的,其实他的执行也挺简单直接使用call进行调用,如
vue 中render执行流程梳理

这个里面的_renderProxy其实是在init.js中绑定的
vue 中render执行流程梳理
vue 中render执行流程梳理
其实就是在开发阶段做一些校验,再将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中定义的

vue 中render执行流程梳理,
而其他的如_e(),_l()这些就是在render-helper中了
vue 中render执行流程梳理

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