vue源码学习二之template编译过程

文章内容输出来源:拉勾教育大前端高薪训练营

在拉钩训练营学习已经有一段时间了,感觉这段时间的收获远比自己独自学习强的多,自己学习的时候经常会因为惰性,无法坚持,在这里有班主任时刻关注你的学习进度(感觉对我这种懒人蛮好的),最重要的是有了一个学习的计划,不用无头苍蝇一样东一点西一点,最后什么都没记住。学习都是需要方法和技巧的,在训练营会有专业的老师为你答疑解惑,同时会教你解决问题的思路及方法,让你能够触类旁通。这篇文章主要是Vue源码学习中的模板编译相关:

模板编译

模板编译的作用

  • Vue2.0使用vNode描述视图及各种交互,用户自己编写vNode比较复杂
  • 用户只需要编译类似于HTML的代码-Vue.js,通过编译器将模板转换为返回vNode的函数
  • .vue文件会被webpack在构建过程中转换为render函数
//带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板
<script>
 Vue.component('comp', {
  template: '<div>I am a comp</div>'
})
 const vm = new Vue({
  el: '#app',
  data: {
   msg: 'Hello compiler'
 },
  methods: {
   handler () {
    console.log('test')
  }
//编译后 render 输出的结果
(function anonymous() {
 with (this) {
  return _c(
   "div",
  { attrs: { id: "app" } },
  [
    _m(0),
    _v(" "),
    _c("p", [_v(_s(msg))]),
    _v(" "),
    _c("comp", { on: { myclick: handler } }),
  ],
   1
 );
}
});
</script>

模板编译的入口(src\platforms\web\entry-runtime-with-compiler.js)

Vue.prototype.$mount = function (
……
 // 把 template 转换成 render 函数src\platforms\web\entry-runtime-with-compiler.js
 const { render, staticRenderFns } = compileToFunctions(template, {
  outputSourceRange: process.env.NODE_ENV !== 'production',
  shouldDecodeNewlines,
  shouldDecodeNewlinesForHref,
  delimiters: options.delimiters,
  comments: options.comments
}, this)
 options.render = render
 options.staticRenderFns = staticRenderFns
……
)
//src\platforms\web\compiler\index.js
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
//src\compiler\index.js
import { createCompilerCreator } from './create-compiler'
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
 ...省略
})
//src\platforms\web\compiler\create-compiler.js
import { createCompileToFunctionFn } from './to-function'

export function createCompilerCreator (baseCompile: Function): Function {
  return function createCompiler (baseOptions: CompilerOptions) {
    function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
      ...省略
      return compiled
    }
    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}

流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ShjluwsP-1595942438182)(https://raw.githubusercontent.com/liannian9/Img/master/img/20200726214851.png)]

compileToFunctions就是我们模板编译的入口文件,而这个文件实际是从
src\platforms\web\compiler\to-function.js中引入的:

export function createCompileToFunctionFn (compile: Function): Function {
  const cache = Object.create(null)

  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = extend({}, options)
    const warn = options.warn || baseWarn
    delete options.warn

    // check cache
    //1.0读取缓存中的CompiledFunctionResult对象,如果有直接返回
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }

    // compile
    //2.0将模板编译为编译对象{render,staticRenderFns},字符串形式的js代码
    const compiled = compile(template, options)

    // turn code into functions
    const res = {}
    const fnGenErrors = []
    //3.0将字符串形式的js代码转换为js方法
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })

  //4.0缓存并返回res对象{render,staticRenderFns}
    return (cache[key] = res)
  }
}

我们可以看到最终编译我们使用了compiler函数,我们看下compiler中做了什么:

function compile (
      template: string,//模板
      options?: CompilerOptions//
    ): CompiledResult {
      const finalOptions = Object.create(baseOptions)//合并baseOptions 与CompilerOptions
      const errors = []
      const tips = []

      let warn = (msg, range, tip) => {
        (tip ? tips : errors).push(msg)
      }

      if (options) {
        // merge custom modules
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }
        // merge custom directives
        if (options.directives) {
          finalOptions.directives = extend(
            Object.create(baseOptions.directives || null),
            options.directives
          )
        }
        // copy other options
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key]
          }
        }
      }

      finalOptions.warn = warn

      const compiled = baseCompile(template.trim(), finalOptions)
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }

所以在compile中主要是用于合并选项,调用baseCompile进行编译记录错误,返回编译好的对象,
baseCompile是编译的核心函数,主要做了三件事情:

function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  //将模板转换为ast抽象语法树,以树形结构描述代码结构
  //什么是抽象语法树?
  //抽象语法树简称AST(Abstract Syntax Tree)
  //使用对象形式描述属性的代码结构
  //此处的抽象语法树是用来描述树形结构的HTML字符串
  //为社么使用抽象语法树?
  //模板字符串转换为AST后,可以使用AST对模板进行优化处理
  //标记模板的静态内容,在patch的时候跳过静态内容
  //patch过程中静态内容不需要对比和重新渲染
  const ast = parse(template.trim(), options)
  //优化语法树
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  //将语法树生成字符串形式的js代码
  const code = generate(ast, options)
  return {
    ast,
    //渲染函数--字符串形式需要new Function转化
    render: code.render,
    //静态渲染函数 生成静态vNode树
    staticRenderFns: code.staticRenderFns
  }
}

我们查看下Vue生成的AST的过程(parse的执行流程借鉴了simpleHtmlparse.js):

我们将模板转换为ast后,在使用optimize对其进行优化:

export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // 标记静态节点
  markStatic(root)
  // 标记静态根节点-是静态节点具有children且不为文本节点
  markStaticRoots(root, false)
}

当优化完成后我们需要将其转化为字符串形式的js代码。

总结

  • 在entry-runtime-with-compiler的$mount中,会对vue实例传入的参数进行处理如果不存在
    render且template存在就会对其进行模板编译处理将其转换为render(返回vNode的函数)
  • 这里面render函数是由compileToFunctions函数返回的,这个函数最终是由
    src\platforms\web\compiler\to-function.js中的createCompileToFunctionFn函数
    执行返回的,在这个函数中我们通过compile函数获得编译对象{render,staticRenderFns},
    字符串形式的js代码,最后将其转换为返回vNode的函数js方法返回(就是render函数)
  • 在compile中会合并选项,调用baseCompile进行编译,返回编译好的对象
  • baseCompile:
    • parse:将模板转换为ast抽象语法树,以树形结构描述代码结构
    • optimize:优化语法树,标记模板的静态内容(静态节点及静态根节点),
      在patch时可以跳过静态根节点
    • generate:将语法树生成字符串形式的js代码
  • baseCompile执行完会返回一个对象里面包含ast语法树,字符串形式的渲染函数render、静态渲染函数
    (staticRenderFns),回到上一级compile函数中它会被作为返回值返回,createCompileToFunctionFn
    返回一个compileToFunctions函数,在这个中会对返回值进行处理将render,staticRenderFns转化为js
    方法并返回,这两个方法就是我们在$mount中通过compileToFunctions函数解构赋值的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值