当学习成为了习惯,常识也就变成了常识。 感激各位的 关注点赞珍藏评论

新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。

前言

这篇文章是 Vue 编译器的最初一部分,前两局部别离是:Vue 源码解读(8)—— 编译器 之 解析、Vue 源码解读(9)—— 编译器 之 优化。

从 HTML 模版字符串开始,解析所有标签以及标签上的各个属性,失去 AST 语法树,而后基于 AST 语法树进行动态标记,首先标记每个节点是否为动态动态,而后进一步标记出动态根节点。这样在后续的更新中就能够跳过这些动态根节点的更新,从而进步性能。

这最初一部分讲的是如何从 AST 生成渲染函数。

指标

深刻了解渲染函数的生成过程,了解编译器是如何将 AST 变成运行时的代码,也就是咱们写的类 html 模版最终变成了什么?

源码解读

入口

/src/compiler/index.js
/** * 在这之前做的所有的事件,只有一个目标,就是为了构建平台特有的编译选项(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 对象上都设置了元素的所有信息,比方,标签信息、属性信息、插槽信息、父节点、子节点等。  // 具体有那些属性,查看 options.start 和 options.end 这两个解决开始和完结标签的办法  const ast = parse(template.trim(), options)  // 优化,遍历 AST,为每个节点做动态标记  // 标记每个节点是否为动态节点,而后进一步标记出动态根节点  // 这样在后续更新中就能够跳过这些动态节点了  // 标记动态根,用于生成渲染函数阶段,生成动态根节点的渲染函数  if (options.optimize !== false) {    optimize(ast, options)  }  // 代码生成,将 ast 转换成可执行的 render 函数的字符串模式  // code = {  //   render: `with(this){return ${_c(tag, data, children, normalizationType)}}`,  //   staticRenderFns: [_c(tag, data, children, normalizationType), ...]  // }  const code = generate(ast, options)  return {    ast,    render: code.render,    staticRenderFns: code.staticRenderFns  }})

generate

/src/compiler/codegen/index.js
/** * 从 AST 生成渲染函数 * @returns { *   render: `with(this){return _c(tag, data, children)}`, *   staticRenderFns: state.staticRenderFns * }  */export function generate(  ast: ASTElement | void,  options: CompilerOptions): CodegenResult {  // 实例化 CodegenState 对象,生成代码的时候须要用到其中的一些货色  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 ? genElement(ast, state) : '_c("div")'  return {    render: `with(this){return ${code}}`,    staticRenderFns: state.staticRenderFns  }}

genElement

/src/compiler/codegen/index.js

浏览倡议

先读最初的 else 模块生成 code 的语句局部,即解决自定义组件和原生标签的 else 分支,了解最终生成的数据格式是什么样的;而后再回头浏览 genChildrengenData,先读 genChildren,代码量少,彻底了解最终生成的数据结构,最初再从上到下去浏览其它的分支。

在浏览以下代码时,请把 Vue 源码解读(8)—— 编译器 之 解析(下) 最初失去的 AST 对象放旁边辅助浏览,因为生成渲染函数的过程就是在解决该对象上泛滥的属性的过程。

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    // 解决动静组件和一般元素(自定义组件、原生标签)    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)    }    return code  }}

genChildren

/src/compiler/codegen/index.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 !== 'template' &&      el.tag !== 'slot'    ) {      // 优化,只有一个子节点 && 子节点的上有 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    // 返回一个数组,数组的每个元素都是一个子节点的渲染函数,    // 格局:['_c(tag, data, children, normalizationType)', ...]    return `[${children.map(c => gen(c, state)).join(',')}]${normalizationType ? `,${normalizationType}` : ''      }`  }}

genNode

/src/compiler/codegen/index.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)  }}

genText

/src/compiler/codegen/index.js
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))    })`}

genComment

/src/compiler/codegen/index.js
export function genComment(comment: ASTText): string {  return `_e(${JSON.stringify(comment.text)})`}

genData

/src/compiler/codegen/index.js
/** * 解决节点上的泛滥属性,最初生成这些属性组成的 JSON 字符串,比方 data = { key: xx, ref: xx, ... }  */export function genData(el: ASTElement, state: CodegenState): string {  // 节点的属性组成的 JSON 字符串  let data = '{'  // 首先先解决指令,因为指令可能在生成其它属性之前扭转这些属性  // 执行指令编译办法,比方 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 }, ...}]   // directives first.  // directives may mutate the el's other properties before they are generated.  const dirs = genDirectives(el, state)  if (dirs) data += dirs + ','  // key,data = { key: xx }  if (el.key) {    data += `key:${el.key},`  }  // ref,data = { ref: xx }  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,`  }  // 动静组件,data = { tag: 'component' }  // record original tag name for components using "is" attribute  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: '_d(动态属性字符串, 动静属性字符串)' }  // 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: '_u(xxx)' }  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(/,$/, '') + '}'  // v-bind dynamic argument wrap  // v-bind with dynamic arguments must be applied using the same v-bind object  // merge helper so that class/style/mustUseProp attrs are handled correctly.  if (el.dynamicAttrs) {    // 存在动静属性,data = `_b(data, tag, 动态属性字符串或者_d(动态属性字符串, 动静属性字符串))`    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}

genDirectives

/src/compiler/codegen/index.js

浏览倡议:这部分内容也能够放到其它办法前面去读,比方你想深究 v-model 的实现原理

/** * 运行指令的编译办法,如果指令存在运行时工作,则返回 directives: [{ name, rawName, value, arg, modifiers }, ...}]  */function genDirectives(el: ASTElement, state: CodegenState): string | void {  // 获取指令数组  const dirs = el.directives  // 没有指令则间接完结  if (!dirs) return  // 指令的处理结果  let res = 'directives:['  // 标记,用于标记指令是否须要在运行时实现的工作,比方 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      // compile-time directive that manipulates AST.      // returns true if it also needs a runtime counterpart.      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)}` : ''        }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''        }${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''        }},`    }  }  if (hasRuntime) {    // 也就是说,只有指令存在运行时工作时,才会返回 res    return res.slice(0, -1) + ']'  }}

genProps

/src/compiler/codegen/index.js
/** * 遍历属性数组 props,失去所有属性组成的字符串 * 如果不存在动静属性,则返回: *   'attrName,attrVal,...' * 如果存在动静属性,则返回: *   '_d(动态属性字符串, 动静属性字符串)'  */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 {      // 动态属性,'attrName,attrVal,...'      staticProps += `"${prop.name}":${value},`    }  }  // 去掉动态属性最初的逗号  staticProps = `{${staticProps.slice(0, -1)}}`  if (dynamicProps) {    // 如果存在动静属性则返回:    // _d(动态属性字符串,动静属性字符串)    return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`  } else {    // 阐明属性数组中不存在动静属性,间接返回动态属性字符串    return staticProps  }}

genHandlers

/src/compiler/codegen/events.js
/** * 生成自定义事件的代码 * 动静:'nativeOn|on_d(staticHandlers, [dynamicHandlers])' * 动态:`nativeOn|on${staticHandlers}` */ export function genHandlers (  events: ASTElementHandlers,  isNative: boolean): string {  // 原生:nativeOn,否则为 on  const prefix = isNative ? 'nativeOn:' : 'on:'  // 动态  let staticHandlers = ``  // 动静  let dynamicHandlers = ``  // 遍历 events 数组  // events = [{ name: { value: 回调函数名, ... } }]  for (const name in events) {    // 获取指定事件的回调函数名,即 this.methodName 或者 [this.methodName1, ...]    const handlerCode = genHandler(events[name])    if (events[name] && events[name].dynamic) {      // 动静,dynamicHandles = `eventName,handleCode,...,`      dynamicHandlers += `${name},${handlerCode},`    } else {      // 动态,staticHandles = `"eventName":handleCode,`      staticHandlers += `"${name}":${handlerCode},`    }  }  // 去掉开端的逗号  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  }}

genStatic

/src/compiler/codegen/index.js
/** * 生成动态节点的渲染函数 *   1、将以后动态节点的渲染函数放到 staticRenderFns 数组中 *   2、返回一个可执行函数 _m(idx, true or '')  */// hoist static sub-trees outfunction genStatic(el: ASTElement, state: CodegenState): string {  // 标记以后动态节点曾经被解决过了  el.staticProcessed = true  // Some elements (templates) need to behave differently inside of a v-pre  // node.  All pre nodes are static roots, so we can use this as a location to  // wrap a state change and reset it upon exiting the pre node.  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 '')  // idx = 以后动态节点的渲染函数在 staticRenderFns 数组中下标  return `_m(${state.staticRenderFns.length - 1    }${el.staticInFor ? ',true' : ''    })`}

genOnce

/src/compiler/codegen/index.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 '')` */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 = ''    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 !== 'production' && state.warn(        `v-once can only be used inside v-for that is keyed. `,        el.rawAttrsMap['v-once']      )      return genElement(el, state)    }    // 生成 `_o(_c(tag, data, children), number, key)`    return `_o(${genElement(el, state)},${state.onceId++},${key})`  } else {    // 下面几种状况都不合乎,阐明就是一个简略的动态节点,和解决动态根节点时的操作一样,    // 失去 _m(idx, true or '')    return genStatic(el, state)  }}

genFor

/src/compiler/codegen/index.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}` : ''  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''  // 提醒,v-for 指令在组件上时必须应用 key  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 */    )  }  // 标记以后节点上的 v-for 指令曾经被解决过了  el.forProcessed = true // avoid r  // 失去 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`  return `${altHelper || '_l'}((${exp}),` +    `function(${alias}${iterator1}${iterator2}){` +    `return ${(altGen || genElement)(el, state)}` +    '})'}

genIf

/src/compiler/codegen/index.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 || '_e()'  }  // 从 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)  }}

genSlot

/src/compiler/codegen/index.js
/** * 生成插槽的渲染函数,失去 * _t(slotName, children, attrs, bind) */function genSlot(el: ASTElement, state: CodegenState): string {  // 插槽名称  const slotName = el.slotName || '"default"'  // 生成所有的子节点  const children = genChildren(el, state)  // 后果字符串,_t(slotName, children, attrs, bind)  let res = `_t(${slotName}${children ? `,${children}` : ''}`  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['v-bind']  if ((attrs || bind) && !children) {    res += `,null`  }  if (attrs) {    res += `,${attrs}`  }  if (bind) {    res += `${attrs ? '' : ',null'},${bind}`  }  return res + ')'}

genComponent

/src/compiler/codegen/index.js
// componentName is el.component, take it as argument to shun flow's pessimistic refinement/** * 生成动静组件的渲染函数 * 返回 `_c(compName, data, children)` */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}` : ''    })`}

总结

  • 面试官 问:简略说一下 Vue 的编译器都做了什么?

    Vue 的编译器做了三件事件:

    • 将组件的 html 模版解析成 AST 对象
    • 优化,遍历 AST,为每个节点做动态标记,标记其是否为动态节点,而后进一步标记出动态根节点,这样在后续更新的过程中就能够跳过这些动态节点了;标记动态根用于生成渲染函数阶段,生成动态根节点的渲染函数
    • 从 AST 生成运行渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,外面寄存了所有的动态节点的渲染函数


  • 面试官:具体说一下渲染函数的生成过程

    大家一说到渲染函数,基本上说的就是 render 函数,其实编译器生成的渲染有两类:

    • 第一类就是一个 render 函数,负责生成动静节点的 vnode
    • 第二类是放在一个叫 staticRenderFns 数组中的动态渲染函数,这些函数负责生成动态节点的 vnode

    渲染函数生成的过程,其实就是在遍历 AST 节点,通过递归的形式,解决每个节点,最初生成形如:_c(tag, attr, children, normalizationType) 的后果。tag 是标签名,attr 是属性对象,children 是子节点组成的数组,其中每个元素的格局都是 _c(tag, attr, children, normalizationTYpe) 的模式,normalization 示意节点的规范化类型,是一个数字 0、1、2,不重要。

    在解决 AST 节点过程中须要大家重点关注也是面试中常见的问题有:

    • 动态节点是怎么解决的

      动态节点的解决分为两步:

      • 将生成动态节点 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


到这里,Vue 编译器 的源码解读就完结了。置信大家在浏览的过程中未免会产生云里雾里的感觉。这个没什么,编译器这块儿的确是比较复杂,能够说是整个框架最难了解也是代码量最大的一部分了。肯定要静下心来多读几遍,遇到无奈了解的中央,肯定要勤入手,通过示例代码加断点调试的形式帮忙本人了解。

当你读完几遍当前,这时候状况可能就会好一些,然而有些中央可能还会有些晕,这没事,失常。毕竟这是一个框架的编译器,要解决的货色太多太多了,你只须要了解其核心思想(模版解析、动态标记、代码生成)就能够了。前面会有 手写 Vue 系列,编译器这部分会有一个简版的实现,帮忙加深对这部分常识的了解。

编译器读完当前,会发现有个不明确的中央:编译器最初生成的代码都是通过 with 包裹的,比方:

<div id="app">  <div v-for="item in arr" :key="item">{{ item }}</div></div>

通过编译后生成:

with (this) {  return _c(    'div',    {      attrs:      {        "id": "app"      }    },    _l(      (arr),      function (item) {        return _c(          'div',          {            key: item          },          [_v(_s(item))]        )      }    ),    0  )}

都晓得,with 语句能够扩大作用域链,所以生成的代码中的 _c、_l、_v、_s 都是 this 上一些办法,也就是说在运行时执行这些办法能够生成各个节点的 vnode。

所以分割后面的常识,响应式数据更新的整个执行过程就是:

  • 响应式拦挡到数据的更新
  • dep 告诉 watcher 进行异步更新
  • watcher 更新时执行组件更新函数 updateComponent
  • 首先执行 vm._render 生成组件的 vnode,这时就会执行编译器生成的函数
  • 问题

    • 渲染函数中的 _c、_l、、_v、_s 等办法是什么?
    • 它们是如何生成 vnode 的?

下一篇文章 Vue 源码解读(11)—— render helper 将会带来这部分常识的具体解读,也是面试常常被问题的:比方:v-for 的原理是什么?

链接

  • 配套视频,微信公众号回复:"精通 Vue 技术栈源码原理视频版" 获取
  • 精通 Vue 技术栈源码原理 专栏
  • github 仓库 liyongning/Vue 欢送 Star

感激各位的:关注点赞珍藏评论,咱们下期见。


当学习成为了习惯,常识也就变成了常识。 感激各位的 关注点赞珍藏评论

新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。