Heim > Web-Frontend > View.js > Hauptteil

Analysieren Sie den Quellcode und sehen Sie, wie der Vue-Compiler die Rendering-Funktion generiert!

青灯夜游
Freigeben: 2022-01-10 19:00:36
nach vorne
2146 Leute haben es durchsucht

In diesem Artikel werden Sie den Quellcode eingehend analysieren, das Problem anhand des Vue-Quellcodes untersuchen und sehen, wie der Vue-Compiler die Rendering-Funktion generiert. Ich hoffe, dass er für alle hilfreich ist.

Analysieren Sie den Quellcode und sehen Sie, wie der Vue-Compiler die Rendering-Funktion generiert!

Die ersten beiden Artikel verstehen hauptsächlich das Parsen und die Optimierung des vue-Compilers:

  • parst die html-Vorlage der Komponente in ein AST-Objektbasierend auf die
  • AST-Syntax Baum
  • Fahren Sie mit der statischen Markierung fort. Markieren Sie zunächst, ob jeder Knoten ein „statischer Knoten“ ist, und markieren Sie dann den statischen „Wurzelknoten“ weiter, sodass die Aktualisierung des statischen Wurzelknotens in nachfolgenden Aktualisierungen übersprungen werden kann, wodurch die Leistung verbessert wird Lass uns unten mehr darüber erfahrenvue Wie der Compiler die laufende Rendering-Funktion aus dem AST-Syntaxbaum generiertTief in den Quellcode

createCompiler()-Methode – Eintrag

Dateispeicherort: /src/compiler/index.js

Die wichtigste Methode ist die generate(ast, options)-Methode, die für die Generierung von Rendering-Funktionen aus dem verantwortlich ist AST

Syntaxbaum.

/*
  在这之前做的所有的事情,只是为了构建平台特有的编译选项(options),比如 web 平台
  1、将 html 模版解析成 ast
  2、对 ast 树进行静态标记
  3、将 ast 生成渲染函数
     - 静态渲染函数放到 code.staticRenderFns 数组中
     - 动态渲染函数 code.render
     - 在将来渲染时执行渲染函数能够得到 vnode
 */
export const createCompiler = createCompilerCreator(function baseCompile(
  template: string,
  options: CompilerOptions
): CompiledResult {
  /* 
   将模版字符串解析为 AST 语法树
   每个节点的 ast 对象上都设置了元素的所有信息,如,标签信息、属性信息、插槽信息、父节点、子节点等
  */
  const ast = parse(template.trim(), options)

  /*
   优化,遍历 AST,为每个节点做静态标记
     - 标记每个节点是否为静态节点,保证在后续更新中跳过这些静态节点
     - 标记出静态根节点,用于生成渲染函数阶段,生成静态根节点的渲染函数
       优化,遍历 AST,为每个节点做静态标记
 */
  if (options.optimize !== false) {
    optimize(ast, options)
  }

  /*
    从 AST 语法树生成渲染函数
    如:code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)"
  */
  const code = generate(ast, options)

  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Nach dem Login kopieren
/src/compiler/index.js

其中最主要的就是 generate(ast, options) 方法,它负责从 AST 语法树生成渲染函数.

/*
   从 AST 生成渲染函数:
    - render 为字符串的代码
    - staticRenderFns 为包含多个字符串的代码,形式为 `with(this){return xxx}`
*/
export function generate (
  ast: ASTElement | void, // ast 对象
  options: CompilerOptions // 编译选项
): CodegenResult {

  /*
    实例化 CodegenState 对象,参数是编译选项,最终得到 state ,其中大部分属性和 options 一样
  */
  const state = new CodegenState(options)

  /* 
   生成字符串格式的代码,比如:'_c(tag, data, children, normalizationType)'
    - data 为节点上的属性组成 JSON 字符串,比如 '{ key: xx, ref: xx, ... }'
    - children 为所有子节点的字符串格式的代码组成的字符串数组,格式:
      `['_c(tag, data, children)', ...],normalizationType`,
    - normalization 是 _c 的第四个参数,表示节点的规范化类型(非重点,可跳过)

    注意:code 并不一定就是 _c,也有可能是其它的,比如整个组件都是静态的,则结果就为 _m(0)
  */
  const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'

  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}
Nach dem Login kopieren

generate() 方法

文件位置:srccompilercodegenindex.js

其中在给 code 赋值时,主要的内容是通过 genElement(ast, state) 方法进行生成的.

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }

  if (el.staticRoot && !el.staticProcessed) {

    /*
      处理静态根节点,生成节点的渲染函数
        1、将当前静态节点的渲染函数放到 staticRenderFns 数组中
        2、返回一个可执行函数 _m(idx, true or '')
    */
    return genStatic(el, state)

  } else if (el.once && !el.onceProcessed) {

    /*
      处理带有 v-once 指令的节点,结果会有三种:
        1、当前节点存在 v-if 指令,得到一个三元表达式,`condition ? render1 : render2`
        2、当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)`
        3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')`
     */
    return genOnce(el, state)

  } else if (el.for && !el.forProcessed) {

    /*
      处理节点上的 v-for 指令,得到:
        `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
    */ 
    return genFor(el, state)

  } else if (el.if && !el.ifProcessed) {

    /*
      处理带有 v-if 指令的节点,最终得到一个三元表达式:`condition ? render1 : render2`
    */
    return genIf(el, state)

  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {

    /*
       当前节点是 template 标签也不是 插槽 和 带有 v-pre 指令的节点时走这里
       生成所有子节点的渲染函数,返回一个数组,格式如:
        `[_c(tag, data, children, normalizationType), ...]`
    */
    return genChildren(el, state) || 'void 0'

  } else if (el.tag === 'slot') {

    /* 生成插槽的渲染函数,得到: `_t(slotName, children, attrs, bind)` */
    return genSlot(el, state)

  } else {
    /*
      component or element
      处理 动态组件 和 普通元素(自定义组件、原生标签、平台保留标签,如 web 平台中的每个 html 标签)
    */

    let code
    if (el.component) {
      /*
        处理动态组件,生成动态组件的渲染函数,得到 `_c(compName, data, children)`
      */
      code = genComponent(el.component, el, state)

    } else {
      // 处理普通元素(自定义组件、原生标签)

      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        /* 
           非普通元素或者带有 v-pre 指令的组件走这里,处理节点的所有属性,返回一个 JSON 字符串,
           比如: '{ key: xx, ref: xx, ... }'
        */
        data = genData(el, state)
      }

      /* 
        处理子节点,得到所有子节点字符串格式的代码组成的数组,格式:
        `['_c(tag, data, children)', ...],normalizationType`
        其中的 normalization 表示节点的规范化类型(非重点,可跳过)
      */
      const children = el.inlineTemplate ? null : genChildren(el, state, true)

      /*
        得到最终的字符串格式的代码,格式:_c(tag, data, children, normalizationType)
      */ 
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`

    }

    /*
      如果提供了 transformCode 方法,则最终的 code 会经过各个模块(module)的该方法处理,
      不过框架没提供这个方法,不过即使处理了,最终的格式也是 _c(tag, data, children)

      module transforms
    */
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }

    // 返回 code
    return code
  }
}
Nach dem Login kopieren

genElement() 方法

文件位置:srccompilercodegenindex.js

/*
  生成所有子节点的渲染函数,返回一个数组,格式如:
   `[_c(tag, data, children, normalizationType), ...]`
 */
export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void {

 // 获取所有子节点
  const children = el.children

  if (children.length) {
    // 第一个子节点
    const el: any = children[0]

    // optimize single v-for
    if (children.length === 1 &&
      el.for &&
      el.tag !== &#39;template&#39; &&
      el.tag !== &#39;slot&#39;
    ) {
      /* 
       优化处理:
         - 条件:只有一个子节点 && 子节点的上有 v-for 指令 && 子节点的标签不为 template 或者 slot
         - 方式:直接调用 genElement 生成该节点的渲染函数,不需要走下面的循环然后调用 genCode 最后得到渲染函数
      */
      const normalizationType = checkSkip
        ? state.maybeComponent(el) ? `,1` : `,0`
        : ``
      return `${(altGenElement || genElement)(el, state)}${normalizationType}`
    }

    // 获取节点规范化类型,返回一个 number: 0、1、2(非重点,可跳过)
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0

    // 是一个函数,负责生成代码的一个函数
    const gen = altGenNode || genNode

    /*
      返回一个数组,其中每个元素都是一个子节点的渲染函数
      格式:[&#39;_c(tag, data, children, normalizationType)&#39;, ...]
    */ 
    return `[${children.map(c => gen(c, state)).join(&#39;,&#39;)}]${
      normalizationType ? `,${normalizationType}` : &#39;&#39;
    }`
  }
}
Nach dem Login kopieren

genChildren() 方法

文件位置:srccompilercodegenindex.js

function genNode (node: ASTNode, state: CodegenState): string {
  // 处理普通元素节点
  if (node.type === 1) {
    return genElement(node, state)
  } else if (node.type === 3 && node.isComment) {
    // 处理文本注释节点
    return genComment(node)
  } else {
    // 处理文本节点
    return genText(node)
  }
}
Nach dem Login kopieren

genNode() 方法

文件位置:srccompilercodegenindex.js

// 得到返回值,格式为:`_e(xxxx)`
export function genComment (comment: ASTText): string {
  return `_e(${JSON.stringify(comment.text)})`
}
Nach dem Login kopieren

genComment() 方法

文件位置:srccompilercodegenindex.js

// 得到返回值,格式为:`_v(xxxxx)`
export function genText (text: ASTText | ASTExpression): string {
  return `_v(${text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))
  })`
}
Nach dem Login kopieren

genText() 方法

文件位置:srccompilercodegenindex.js

/*
  处理节点上的众多属性,最后生成这些属性组成的 JSON 字符串,
  比如 data = { key: xx, ref: xx, ... } 
*/
export function genData(el: ASTElement, state: CodegenState): string {

  // 节点的属性组成的 JSON 字符串
  let data = &#39;{&#39;

  /*
    首先先处理指令,因为指令可能在生成其它属性之前改变这些属性
    执行指令编译方法,如 web 平台的 v-text、v-html、v-model,然后在 el 对象上添加相应的属性,
    如 v-text:el.textContent = _s(value, dir)
       v-html:el.innerHTML = _s(value, dir)

    当指令在运行时还有任务时,比如 v-model,
    则返回 directives: [{ name, rawName, value, arg, modifiers }, ...}] 
  */
  const dirs = genDirectives(el, state)

  if (dirs) data += dirs + &#39;,&#39;

  // key,data = { key: xxx }
  if (el.key) {
    data += `key:${el.key},`
  }
  // ref,data = { ref: xxx }
  if (el.ref) {
    data += `ref:${el.ref},`
  }
  // 带有 ref 属性的节点在带有 v-for 指令的节点的内部,data = { refInFor: true }
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // pre,v-pre 指令,data = { pre: true }
  if (el.pre) {
    data += `pre:true,`
  }
  // 动态组件 <component is="xxx">,data = { tag: &#39;component&#39; }
  if (el.component) {
    data += `tag:"${el.tag}",`
  }
  /*
    为节点执行模块 (class、style) 的 genData 方法,
    得到 data = { staticClass: xx, class: xx, staticStyle: xx, style: xx }

    module data generation functions
  */
  for (let i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el)
  }
  /*
    其它属性,得到 data = { attrs: 静态属性字符串 } 或者 
    data = { attrs: &#39;_d(静态属性字符串, 动态属性字符串)&#39; }

    attributes
  */
  if (el.attrs) {
    data += `attrs:${genProps(el.attrs)},`
  }
  // DOM props,结果 el.attrs 相同
  if (el.props) {
    data += `domProps:${genProps(el.props)},`
  }
  /*
    自定义事件
     - data = { `on${eventName}:handleCode` } 
             或者 
     - { `on_d(${eventName}:handleCode`, `${eventName},handleCode`) }

      event handlers
  */
  if (el.events) {
    data += `${genHandlers(el.events, false)},`
  }
  /* 
    带 .native 修饰符的事件,
     - data = { `nativeOn${eventName}:handleCode` } 
              或者 
     - { `nativeOn_d(${eventName}:handleCode`, `${eventName},handleCode`)
  */
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true)},`
  }
  /*
   非作用域插槽,得到 data = { slot: slotName }

   slot target
   only for non-scoped slots
  */
  if (el.slotTarget && !el.slotScope) {
    data += `slot:${el.slotTarget},`
  }
  // scoped slots,作用域插槽,data = { scopedSlots: &#39;_u(xxx)&#39; }
  if (el.scopedSlots) {
    data += `${genScopedSlots(el, el.scopedSlots, state)},`
  }
  /*
    处理 v-model 属性,得到
    data = { model: { value, callback, expression } }

    component v-model
  */
  if (el.model) {
    data += `model:{value:${el.model.value
      },callback:${el.model.callback
      },expression:${el.model.expression
      }},`
  }
  /*
     inline-template,处理内联模版,得到:
     data = { inlineTemplate: { render: function() { render 函数 }, staticRenderFns: [ function() {}, ... ] } }
  */
  if (el.inlineTemplate) {
    const inlineTemplate = genInlineTemplate(el, state)
    if (inlineTemplate) {
      data += `${inlineTemplate},`
    }
  }
  // 删掉 JSON 字符串最后的 逗号,然后加上闭合括号 }
  data = data.replace(/,$/, &#39;&#39;) + &#39;}&#39;
  
  /*
    v-bind 动态参数包装
    必须使用相同的 v-bind 对象应用动态绑定参数
    合并辅助对象,以便正确处理 class/style/mustUseProp 属性。
  */
  if (el.dynamicAttrs) {
    data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
  }
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data)
  }
  // v-on data wrap
  if (el.wrapListeners) {
    data = el.wrapListeners(data)
  }
  return data
}
Nach dem Login kopieren

genData() 方法

文件位置:srccompilercodegenindex.js

/**
  运行指令的编译方法,如果指令存在运行时任务,则返回 
  directives: [{ name, rawName, value, arg, modifiers }, ...}] 
*/
function genDirectives(el: ASTElement, state: CodegenState): string | void {
  // 获取指令数组
  const dirs = el.directives
  // 不存在指令,直接结束 
  if (!dirs) return

  // 指令的处理结果
  let res = &#39;directives:[&#39;
  // 用于标记指令是否需要在运行时完成的任务,比如 v-model 的 input 事件
  let hasRuntime = false
  let i, l, dir, needRuntime

  // 遍历指令数组
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    // 获取节点当前指令的处理方法,比如 web 平台的 v-html、v-text、v-model
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // 执行指令的编译方法,如果指令还需要运行时完成一部分任务,则返回 true,比如 v-model
      needRuntime = !!gen(el, dir, state.warn)
    }
    if (needRuntime) {
      // 表示该指令在运行时还有任务
      hasRuntime = true
       // res = directives:[{ name, rawName, value, arg, modifiers }, ...]
      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : &#39;&#39;
        }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : &#39;&#39;
        }${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : &#39;&#39;
        }},`
    }
  }

  // 只有指令存在运行时任务时,才会返回 res
  if (hasRuntime) {
    return res.slice(0, -1) + &#39;]&#39;
  }
}
Nach dem Login kopieren

genDirectives() 方法

文件位置:srccompilercodegenindex.js

/*
  遍历属性数组 props,得到所有属性组成的字符串
  如果不存在动态属性,则返回:&#39;attrName,attrVal,...&#39;
  如果存在动态属性,则返回:&#39;_d(静态属性字符串, 动态属性字符串)&#39; 
 */
function genProps(props: Array<ASTAttr>): string {
  // 静态属性
  let staticProps = ``
  // 动态属性
  let dynamicProps = ``

  // 遍历属性数组
  for (let i = 0; i < props.length; i++) {
    // 属性
    const prop = props[i]
    // 属性值
    const value = __WEEX__
      ? generateValue(prop.value)
      : transformSpecialNewlines(prop.value)

    if (prop.dynamic) {
       // 动态属性,`dAttrName,dAttrVal,...`
      dynamicProps += `${prop.name},${value},`
    } else {
      // 静态属性,&#39;attrName:attrVal,...&#39;
      staticProps += `"${prop.name}":${value},`
    }
  }
  // 闭合静态属性字符串,并去掉静态属性最后的 &#39;,&#39;
  staticProps = `{${staticProps.slice(0, -1)}}`

  if (dynamicProps) {
    // 如果存在动态属性则返回:_d(静态属性字符串,动态属性字符串)
    return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
  } else {
    // 说明属性数组中不存在动态属性,直接返回静态属性字符串
    return staticProps
  }
}
Nach dem Login kopieren

genDirectives() 方法

文件位置:srccompilercodegenindex.js

/*
  生成自定义事件的代码
  动态:&#39;nativeOn|on_d(staticHandlers, [dynamicHandlers])&#39;
  静态:`nativeOn|on${staticHandlers}`
 */
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean
): string {
  // 原生为 nativeOn,否则为 on
  const prefix = isNative ? &#39;nativeOn:&#39; : &#39;on:&#39;
  // 静态
  let staticHandlers = ``
  // 动态
  let dynamicHandlers = ``
  /*
    遍历 events 数组
    events = [{ name: { value: 回调函数名, ... } }]
  */ 
  for (const name in events) {
    const handlerCode = genHandler(events[name])
    if (events[name] && events[name].dynamic) {
      // 动态,dynamicHandles = `eventName,handleCode,...,`
      dynamicHandlers += `${name},${handlerCode},`
    } else {
      // staticHandlers = `eventName:handleCode,...,`
      staticHandlers += `"${name}":${handlerCode},`
    }
  }

  // 闭合静态事件处理代码字符串,去除末尾的 &#39;,&#39;
  staticHandlers = `{${staticHandlers.slice(0, -1)}}`

  if (dynamicHandlers) {
    // 动态,on_d(statickHandles, [dynamicHandlers])
    return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
  } else {
    // 静态,`on${staticHandlers}`
    return prefix + staticHandlers
  }
}
Nach dem Login kopieren

genHandlers() 方法

文件位置:srccompilercodegenevents.js

/*
  生成静态节点的渲染函数
    1、将当前静态节点的渲染函数放到 staticRenderFns 数组中
    2、返回一个可执行函数 _m(idx, true or &#39;&#39;) 
  
  hoist static sub-trees out
*/
function genStatic(el: ASTElement, state: CodegenState): string {
  // 标记当前静态节点已经被处理过了
  el.staticProcessed = true

  /*
    某些元素(模板)在 v-pre 节点中需要有不同的行为
    所有 pre 节点都是静态根,因此可将其用作包装状态更改并在退出 pre 节点时将其重置
  */
  const originalPreState = state.pre
  if (el.pre) {
    state.pre = el.pre
  }

  /* 
    将静态根节点的渲染函数 push 到 staticRenderFns 数组中,
    比如:[`with(this){return _c(tag, data, children)}`]
  */
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)

  state.pre = originalPreState
  /* 
    返回一个可执行函数:_m(idx, true or &#39;&#39;)
    idx = 当前静态节点的渲染函数在 staticRenderFns 数组中下标
  */
  return `_m(${state.staticRenderFns.length - 1
    }${el.staticInFor ? &#39;,true&#39; : &#39;&#39;
    })`
}
Nach dem Login kopieren

genStatic() 方法

文件位置:srccompilercodegenindex.js

/*
 处理带有 v-once 指令的节点,结果会有三种:
   1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2
   2、当前节点是一个包含在 v-for 指令内部的静态节点,
      得到 `_o(_c(tag, data, children), number, key)`
   3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of &#39;&#39;)`
 
  v-once
 */
function genOnce(el: ASTElement, state: CodegenState): string {
  // 标记当前节点的 v-once 指令已经被处理过了
  el.onceProcessed = true
  if (el.if && !el.ifProcessed) {
    /*
     如果含有 v-if 指令 && if 指令没有被处理过
     则处理带有 v-if 指令的节点,最终得到一个三元表达式:
       condition ? render1 : render2 
    */ 
    return genIf(el, state)

  } else if (el.staticInFor) {
    /*
      说明当前节点是被包裹在还有 v-for 指令节点内部的静态节点
      获取 v-for 指令的 key
    */
    let key = &#39;&#39;
    let parent = el.parent
    while (parent) {
      if (parent.for) {
        key = parent.key
        break
      }
      parent = parent.parent
    }

    // key 不存在则给出提示,v-once 节点只能用于带有 key 的 v-for 节点内部
    if (!key) {
      process.env.NODE_ENV !== &#39;production&#39; && state.warn(
        `v-once can only be used inside v-for that is keyed. `,
        el.rawAttrsMap[&#39;v-once&#39;]
      )

      return genElement(el, state)
    }

    // 生成 `_o(_c(tag, data, children), number, key)`
    return `_o(${genElement(el, state)},${state.onceId++},${key})`
  } else {
     /*
       上面几种情况都不符合,说明就是一个简单的静态节点,
       和处理静态根节点时的操作一样,得到 _m(idx, true or &#39;&#39;)
     */ 
    return genStatic(el, state)
  }
}
Nach dem Login kopieren

genOnce() 方法

文件位置:srccompilercodegenindex.js

/*
  处理节点上 v-for 指令  
  得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
*/
export function genFor(
  el: any,
  state: CodegenState,
  altGen?: Function,
  altHelper?: string
): string {
  // v-for 的迭代器,比如 一个数组
  const exp = el.for
  // 迭代时的别名
  const alias = el.alias
  // iterator 为 v-for = "(item ,idx) in obj" 时会有,比如 iterator1 = idx
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : &#39;&#39;
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : &#39;&#39;

  // 提示,v-for 指令在组件上时必须使用 key
  if (process.env.NODE_ENV !== &#39;production&#39; &&
    state.maybeComponent(el) &&
    el.tag !== &#39;slot&#39; &&
    el.tag !== &#39;template&#39; &&
    !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[&#39;v-for&#39;],
      true /* tip */
    )
  }

  // 标记当前节点上的 v-for 指令已经被处理过了
  el.forProcessed = true // avoid recursion

  // 返回 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
  return `${altHelper || &#39;_l&#39;}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
    `return ${(altGen || genElement)(el, state)}` +
    &#39;})&#39;
}
Nach dem Login kopieren

genFor() 方法

文件位置:srccompilercodegenindex.js

// 处理带有 v-if 指令的节点,最终得到一个三元表达式,condition ? render1 : render2 
export function genIf(
  el: any,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  // 标记当前节点的 v-if 指令已经被处理过了,避免无效的递归
  el.ifProcessed = true // avoid recursion
  // 得到三元表达式,condition ? render1 : render2
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions(
  conditions: ASTIfConditions,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {

  // 长度若为空,则直接返回一个空节点渲染函数
  if (!conditions.length) {
    return altEmpty || &#39;_e()&#39;
  }

  // 从 conditions 数组中拿出第一个条件对象 { exp, block }
  const condition = conditions.shift()
  // 返回结果是一个三元表达式字符串,condition ? 渲染函数1 : 渲染函数2
  if (condition.exp) {
    /*
     如果 condition.exp 条件成立,则得到一个三元表达式,
     如果条件不成立,则通过递归的方式找 conditions 数组中下一个元素,
     直到找到条件成立的元素,然后返回一个三元表达式
    */
    return `(${condition.exp})?${genTernaryExp(condition.block)
      }:${genIfConditions(conditions, state, altGen, altEmpty)
      }`
  } else {
    return `${genTernaryExp(condition.block)}`
  }

  // v-if with v-once should generate code like (a)?_m(0):_m(1)
  function genTernaryExp(el) {
    return altGen
      ? altGen(el, state)
      : el.once
        ? genOnce(el, state)
        : genElement(el, state)
  }
}
Nach dem Login kopieren

genIf() 方法

文件位置:srccompilercodegenindex.js

/*
  生成插槽的渲染函数,得到:_t(slotName, children, attrs, bind)
 */
function genSlot(el: ASTElement, state: CodegenState): string {
   // 插槽名称
  const slotName = el.slotName || &#39;"default"&#39;
  // 生成所有的子节点
  const children = genChildren(el, state)
  // 结果字符串,_t(slotName, children, attrs, bind)
  let res = `_t(${slotName}${children ? `,function(){return ${children}}` : &#39;&#39;}`
  
  const attrs = el.attrs || el.dynamicAttrs
    ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({
      // slot props are camelized
      name: camelize(attr.name),
      value: attr.value,
      dynamic: attr.dynamic
    })))
    : null

  const bind = el.attrsMap[&#39;v-bind&#39;]
  
  if ((attrs || bind) && !children) {
    res += `,null`
  }
  if (attrs) {
    res += `,${attrs}`
  }
  if (bind) {
    res += `${attrs ? &#39;&#39; : &#39;,null&#39;},${bind}`
  }
  return res + &#39;)&#39;
}
Nach dem Login kopieren

genSlot() 方法

文件位置:srccompilercodegenindex.js

/*
  生成动态组件的渲染函数,返回 `_c(compName, data, children)`

  componentName is el.component, take it as argument to shun flow&#39;s pessimistic refinement
*/
function genComponent(
  componentName: string,
  el: ASTElement,
  state: CodegenState
): string {
   // 所有的子节点
  const children = el.inlineTemplate ? null : genChildren(el, state, true)
  // 返回 `_c(compName, data, children)`,compName 是 is 属性的值
  return `_c(${componentName},${genData(el, state)}${children ? `,${children}` : &#39;&#39;
    })`
}
Nach dem Login kopieren

genComponent() 方法

文件位置:srccompilercodegenindex.js

rrreee

总结

渲染函数的生成过程是什么?

编译器生成的渲染有两类:

  • render 函数,负责生成动态节点的 vnode
  • staticRenderFns 数组中的 静态渲染函数,负责生成静态节点的 vnode

渲染函数的生成过程,其实就是在遍历 AST 节点,通过递归的方式处理每个节点,最后生成格式如:_c(tag, attr, children, normalizationType)

generate()-Methode🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜🎜Beim Zuweisen eines Werts zu 🎜code🎜, der Hauptinhalt wird durch 🎜 genElement(ast, state)🎜 Methode generiert.🎜rrreee🎜🎜🎜genElement() Methode🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜rrreee🎜 🎜🎜genChildren()-Methode 🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genNode()-Methode 🎜🎜 🎜🎜🎜Dateispeicherort: srccompilercodegenindex.j s Dateispeicherort: <code>srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genData() method🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex. js🎜🎜rrreee🎜🎜🎜genDirectives()-Methode🎜🎜 🎜🎜 🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genDirectives()-Methode 🎜🎜🎜 🎜🎜Speicherort der Datei : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genHandlers ( ) Methode 🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenevents.js🎜🎜rrreee🎜🎜🎜genStatic() Methode 🎜 🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜 rrreee🎜🎜🎜genOnce()-Methode 🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜rrreee 🎜🎜 🎜genFor()-Methode 🎜🎜🎜🎜🎜Dateispeicherort: srccompilercode genindex.js 🎜🎜rrreee🎜🎜🎜genIf()-Methode 🎜🎜🎜🎜🎜Dateispeicherort: srccompilercodegenindex.js code>🎜🎜rrreee🎜🎜🎜genSlot()-Methode 🎜🎜🎜🎜🎜 Dateispeicherort: <code>srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genComponent()-Methode 🎜🎜🎜🎜 🎜Dateispeicherort: srccompilercodegenindex.js🎜🎜rrreee🎜Zusammenfassung🎜
🎜🎜rendering Was ist der Funktionsgenerierungsprozess? 🎜🎜
🎜Es gibt zwei Arten von Renderings, die vom Compiler generiert werden: 🎜🎜🎜render-Funktion, die für die Generierung von vnode dynamischer Knoten verantwortlich ist🎜 🎜staticRenderFns Die statische Renderfunktion im Array ist verantwortlich zum Generieren des vnode von statischen Knoten. Der Generierungsprozess der Rendering-Funktion durchläuft tatsächlich den AST-Knoten, jeder Knoten wird rekursiv verarbeitet und das endgültige generierte Format ist: _c(tag, attr , Kinder, Normalisierungstyp): 🎜
  • tag ist der Tag-Name tag 是标签名
  • attr 是属性对象
  • children 是子节点组成的数组,其中每个元素的格式都是 _c(tag, attr, children, normalizationTYpe) 的形式,
  • normalization 表示节点的规范化类型,是一个数字 0、1、2

静态节点是怎么处理的?

静态节点的处理分为两步:

  • 将生成静态节点 vnode 函数放到 staticRenderFns 数组中
  • 返回一个 _m(idx) 的可执行函数,即执行 staticRenderFns 数组中下标为 idx 的函数,生成静态节点的 vnode

v-once、v-if、v-for、组件 等都是怎么处理的?

  • 单纯的 v-once 节点处理方式 和 静态节点 一致
  • v-if 节点的处理结果是一个 三元表达式
  • v-for 节点的处理结果是可执行的 _l 函数,该函数负责生成 v-for 节点的 vnode
  • 组件的处理结果和普通元素一样,得到的是形如 _c(compName) 的可执行代码,生成组件的 vnode
  • attr ist das Attributobjekt

children ist ein Array bestehend aus untergeordneten Knoten, wobei das Format von Jedes Element hat die Form _c(tag, attr, children, normalizationTYpe), normalization stellt den Normalisierungstyp des Knotens dar, der eine Zahl 0 ist , 1, 2

🎜
🎜Wie werden statische Knoten verarbeitet?🎜
🎜Die Verarbeitung statischer Knoten ist in zwei Schritte unterteilt: 🎜🎜🎜Stellen Sie die Funktion zum Generieren statischer Knoten ein vnode into staticRenderFns gibt eine ausführbare Funktion von _m(idx) im Array zurück, d. h. führt staticRenderFns aus. Der Index im Array ist die Funktion idx zum Generieren statischer Knoten vnode🎜🎜
🎜Wie sind v-once, v-if, v-for, Komponenten usw. verarbeitet?🎜
🎜🎜Einfache v-once-Knotenverarbeitungsmethode ist die gleiche wie statischer Knoten🎜🎜v -if-Knotenverarbeitungsergebnis ist ein Das Verarbeitungsergebnis des ternären Ausdrucks🎜🎜v-for-Knotens ist die ausführbare _l-Funktion, welches für die Generierung von v verantwortlich ist. Das Verarbeitungsergebnis der <code>vnode🎜🎜-Komponente des for-Knotens ist das gleiche wie das eines gewöhnlichen Elements. Es wird eine ausführbare Datei erhalten Code in Form von _c(compName) und dem vnode der Komponente🎜🎜🎜[Verwandte Empfehlungen: 🎜vue.js-Tutorial🎜]🎜

Das obige ist der detaillierte Inhalt vonAnalysieren Sie den Quellcode und sehen Sie, wie der Vue-Compiler die Rendering-Funktion generiert!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage