vue源码学习总结 深入解析template编译成render函数过程

简述编译流程

总的来说,在beforeMount之前执行编译过程,第一步通过html-parser将template解析成ast抽象语法树,第二步通过optimize优化ast并标记静态节点和静态根节点,第三步通过generate将ast抽象语法树编译成render字符串并将静态部分放到staticRenderFns中,最后通过new Function(render)生成render函数。在beforeMount和mounted之间执行render函数生成VNode,然后通过patch(VNode)生成dom树并挂载,调用mounted。

编译入口

//entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function ( //挂载前先做一些预处理
  el?: string | Element,
  hydrating?: boolean
): Component {
   
  const {
    render, staticRenderFns } = compileToFunctions(template, {
   
    shouldDecodeNewlines,
    delimiters: options.delimiters,
    comments: options.comments
  }, this)
  options.render = render
  options.staticRenderFns = staticRenderFns
  return mount.call(this, el, hydrating)
}
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
   
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
   
  vm.$el = el
  callHook(vm, 'beforeMount')
  let updateComponent = () => {
   
    vm._update(vm._render(), hydrating)//调用render生成VNode,然后patch渲染到页面
  }
  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false
  if (vm.$vnode == null) {
   
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

由此可知,在beforeMount之前执行编译过程,然后调用beforeMount,然后执行render函数,然后调用mounted

编译过程

//src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
   
  const ast = parse(template.trim(), options) //生成抽象语法树
  optimize(ast, options) //优化抽象语法树,标记静态节点和静态根节点
  const code = generate(ast, options) //生成render函数字符串
  return {
   
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns //静态无需重新渲染的部分
  }
})

export function createCompilerCreator (baseCompile: Function): Function {
   
  return function createCompiler (baseOptions: CompilerOptions) {
   
    function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
   
      const finalOptions = Object.create(baseOptions)
      const compiled = baseCompile(template, finalOptions)
      return compiled
    }
    return {
   
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
export function createCompileToFunctionFn (compile: Function): Function {
   
  const cache: {
   
    [key: string]: CompiledFunctionResult;
  } = Object.create(null)

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

    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
   
      return cache[key]
    }

    const compiled = compile(template, options)

    const res = {
   }
    const fnGenErrors = []
    res.render = createFunction(compiled.render, fnGenErrors) // 将render字符串转换成函数
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
   
      return createFunction(code, fnGenErrors) //将静态render字符串转换成函数
    })
    return (cache[key] = res) //缓存结果,当template不变时直接返回缓存
  }
}

function createFunction (code, errors) {
   
  try {
   
    return new Function(code)
  } catch (err) {
   
    errors.push({
    err, code })
    return noop
  }
}

由此可知,编译过程为:第一步通过html-parser将template解析成ast抽象语法树,第二步通过optimize优化ast并标记静态节点和静态根节点,第三步通过generate将ast抽象语法树编译成render字符串并将静态部分放到staticRenderFns中,最后通过new Function(render)生成render函数。

将template解析成ast抽象语法树

src/compiler/index.js
src/compiler/parser/html-parser.js

export function parse (
  template: string,
  options: CompilerOptions
): ASTElement | void {
   

  const stack = []
  const preserveWhitespace = options.preserveWhitespace !== false
  let root
  let currentParent
  let inVPre = false
  let inPre = false
  let warned = false
  
  parseHTML(template, {
   
    warn,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldKeepComment: options.comments,
    start (tag, attrs, unary) {
   
      // check namespace.
      // inherit parent ns if there is one
      const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

      // handle IE svg bug
      /* istanbul ignore if */
      if (isIE && ns === 'svg') {
   
        attrs = guardIESVGBug(attrs)
      }

      let element: ASTElement = createASTElement(tag, attrs, currentParent)
      if (ns) {
   
        element.ns = ns
      }

      if (isForbiddenTag(element) && !isServerRendering()) {
   
        element.forbidden = true
        process.env.NODE_ENV !== 'production' && warn(
          'Templates should only be responsible for mapping the state to the ' +
          'UI. Avoid placing tags with side-effects in your templates, such as ' +
          `<${
     tag}>` + ', as they will not be parsed.'
        )
      }

      // apply pre-transforms
      for (let i = 0; i < preTransforms.length; i++) {
   
        element = preTransforms[i](element, options) || element
      }

      if (!inVPre) {
   
        processPre(element)
        if (element.pre) {
   
          inVPre = true
        }
      }
      if (platformIsPreTag(element.tag
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值