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

  • 相关阅读:
    Android压缩图片到100K以下并保持不失真的高效方法
    Android 动画学习笔记
    JAVA jdbc连接Mysql
    android 在myeclipse中查看源码
    Intent用法总结
    Android中加载数据库到data/data下的当前包中
    Android Paint类介绍
    经典常用SQL语句大全
    android 语音识别技术
    Insert multi user or more than one user in SharePoint list Person or Group field
  • 原文地址:https://www.cnblogs.com/dengxiaoning/p/12871797.html
Copyright © 2011-2022 走看看