美文网首页程序员
vue templete 编译过程,optimize,gener

vue templete 编译过程,optimize,gener

作者: 臣以君纲 | 来源:发表于2019-02-25 00:21 被阅读0次

    之前有提到,vue中的templete最后都会编译成render函数来执行,编译的过程主要由parse,optimize,generate组成,下面我们来聊聊optimize和generate

    optimize,翻译成中文是优化,而optimize完成的正是优化的过程,optimize通过对节点添加标志,从而在页面重新刷新进行vnode比对时,跳过静态节点,提高性能,下面我们进入源码

    export function optimize (root: ?ASTElement, options: CompilerOptions) {
      if (!root) return
      isStaticKey = genStaticKeysCached(options.staticKeys || '')
      isPlatformReservedTag = options.isReservedTag || no
      // first pass: mark all non-static nodes.
      markStatic(root)
      // second pass: mark static roots.
      markStaticRoots(root, false)
    }
    

    主要执行两个方法,markStaticmarkStaticRoots,root即为通过parse阶段生成的ast抽象语法树
    我们进入markStatic

    function markStatic (node: ASTNode) {
      node.static = isStatic(node)
      if (node.type === 1) {
        // do not make component slot content static. this avoids
        // 1. components not able to mutate slot nodes
        // 2. static slot content fails for hot-reloading
        if (
          !isPlatformReservedTag(node.tag) &&
          node.tag !== 'slot' &&
          node.attrsMap['inline-template'] == null
        ) {
          return
        }
        for (let i = 0, l = node.children.length; i < l; i++) {
          const child = node.children[i]
          markStatic(child)
          if (!child.static) {
            node.static = false
          }
        }
        if (node.ifConditions) {
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            const block = node.ifConditions[i].block
            markStatic(block)
            if (!block.static) {
              node.static = false
            }
          }
        }
      }
    }
    

    首先调用isStatic

    function isStatic (node: ASTNode): boolean {
      if (node.type === 2) { // expression
        return false
      }
      if (node.type === 3) { // text
        return true
      }
      return !!(node.pre || (
        !node.hasBindings && // no dynamic bindings
        !node.if && !node.for && // not v-if or v-for or v-else
        !isBuiltInTag(node.tag) && // not a built-in
        isPlatformReservedTag(node.tag) && // not a component
        !isDirectChildOfTemplateFor(node) &&
        Object.keys(node).every(isStaticKey)
      ))
    }
    

    可以看到,如果node 是表达式,返回false,如果是文本节点,返回true,如果有v-for click等的属性也返回false,
    然后回到markStatic,可以看到会对node的children子节点递归调用markStaic,如果子节点不是static,就会把当前节点也置为非static,
    这就是markStatic的主要过程,
    下面我们进入markstaticRoot

    function markStaticRoots (node: ASTNode, isInFor: boolean) {
      if (node.type === 1) {
        if (node.static || node.once) {
          node.staticInFor = isInFor
        }
        // For a node to qualify as a static root, it should have children that
        // are not just static text. Otherwise the cost of hoisting out will
        // outweigh the benefits and it's better off to just always render it fresh.
        if (node.static && node.children.length && !(
          node.children.length === 1 &&
          node.children[0].type === 3
        )) {
          node.staticRoot = true
          return
        } else {
          node.staticRoot = false
        }
        if (node.children) {
          for (let i = 0, l = node.children.length; i < l; i++) {
            markStaticRoots(node.children[i], isInFor || !!node.for)
          }
        }
        if (node.ifConditions) {
          for (let i = 1, l = node.ifConditions.length; i < l; i++) {
            markStaticRoots(node.ifConditions[i].block, isInFor)
          }
        }
      }
    }
    

    可以看到,如果一个节点是static的,且他有子节点,且不是只有一个children,那就会把节点标记staticRoot,并会对子节点进行递归调用,
    从而对所有节点进行标记,这就是optimize的主要内容,

    编译的最后一个步骤是generate,这里是真正的生成render函数,最后返回的是一个字符串,可由new Function或eval进行执行

    export function generate (
      ast: ASTElement | void,
      options: CompilerOptions
    ): CodegenResult {
      const state = new CodegenState(options)
      const code = ast ? genElement(ast, state) : '_c("div")'
      return {
        render: `with(this){return ${code}}`,
        staticRenderFns: state.staticRenderFns
      }
    }
    

    主要执行了genElement方法,我们进入genElement方法

    export function genElement (el: ASTElement, state: CodegenState): string {
      if (el.parent) {
        el.pre = el.pre || el.parent.pre
      }
    
      if (el.staticRoot && !el.staticProcessed) {
        return genStatic(el, state)
      } else if (el.once && !el.onceProcessed) {
        return genOnce(el, state)
      } else if (el.for && !el.forProcessed) {
        return genFor(el, state)
      } else if (el.if && !el.ifProcessed) {
        return genIf(el, state)
      } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
        return genChildren(el, state) || 'void 0'
      } else if (el.tag === 'slot') {
        return genSlot(el, state)
      } else {
        // component or element
        let code
        if (el.component) {
          code = genComponent(el.component, el, state)
        } else {
          let data
          if (!el.plain || (el.pre && state.maybeComponent(el))) {
            data = genData(el, state)
          }
    
          const children = el.inlineTemplate ? null : genChildren(el, state, true)
          code = `_c('${el.tag}'${
            data ? `,${data}` : '' // data
          }${
            children ? `,${children}` : '' // children
          })`
        }
        // module transforms
        for (let i = 0; i < state.transforms.length; i++) {
          code = state.transforms[i](el, code)
        }
        return code
      }
    }
    

    可以看到,这时会根据不同的节点属性执行不同的方法,例如如果只是普通的节点会或返回一个code字符串,内容为'_c(xx',_c这些方法是什么呢,其实这些方法都定义在辅助函数中

    export function installRenderHelpers (target: any) {
      target._o = markOnce
      target._n = toNumber
      target._s = toString
      target._l = renderList
      target._t = renderSlot
      target._q = looseEqual
      target._i = looseIndexOf
      target._m = renderStatic
      target._f = resolveFilter
      target._k = checkKeyCodes
      target._b = bindObjectProps
      target._v = createTextVNode
      target._e = createEmptyVNode
      target._u = resolveScopedSlots
      target._g = bindObjectListeners
    }
    

    因为情况非常多,我们就拿v-for来举例说明一下执行过程,实例templete为<ul><li v-for="item of [1,2,3]">{{item}}</li></ul>, genFor源码

    export function genFor (
      el: any,
      state: CodegenState,
      altGen?: Function,
      altHelper?: string
    ): string {
      const exp = el.for
      const alias = el.alias
      const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
      const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
    
      if (process.env.NODE_ENV !== 'production' &&
        state.maybeComponent(el) &&
        el.tag !== 'slot' &&
        el.tag !== 'template' &&
        !el.key
      ) {
        state.warn(
          `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
          `v-for should have explicit keys. ` +
          `See https://vuejs.org/guide/list.html#key for more info.`,
          el.rawAttrsMap['v-for'],
          true /* tip */
        )
      }
    
      el.forProcessed = true // avoid recursion
      return `${altHelper || '_l'}((${exp}),` +
        `function(${alias}${iterator1}${iterator2}){` +
          `return ${(altGen || genElement)(el, state)}` +
        '})'
    }
    

    可以看到首先对templete类型的key进行了判断,如果没有,会进行警告,最后返回了一个_l作为函数名的执行函数,参数为定义的v-for值,函数内容为另一个genElement,最后整个templete返回的render函数内容为

    "with(this){return _c('ul',_l(([1,2,3]),function(item){return _c('li',[_v(_s(item))])}),0)}"
    

    最后render函数在执行时就会把_c和_l等替换成真正的函数,从而返回一个vnode,再继续完成patch,插入真实dom树完成渲染,
    到现在为止,templete 编译过程已经完成。

    相关文章

      网友评论

        本文标题:vue templete 编译过程,optimize,gener

        本文链接:https://www.haomeiwen.com/subject/vwyiyqtx.html