特殊说明

由于文章篇幅限制,所以将 Vue 源码解读(8)—— 编译器 之 解析 拆成了两篇文章,本篇是对 Vue 源码解读(8)—— 编译器 之 解析(上) 的一个补充,所以在阅读时请同时打开 Vue 源码解读(8)—— 编译器 之 解析(上) 一起阅读。

processAttrs

/src/compiler/parser/index.js

  1. /**
  2. * 处理元素上的所有属性:
  3. * v-bind 指令变成:el.attrs 或 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...],
  4. * 或者是必须使用 props 的属性,变成了 el.props = [{ name, value, start, end, dynamic }, ...]
  5. * v-on 指令变成:el.events 或 el.nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] }
  6. * 其它指令:el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
  7. * 原生属性:el.attrs = [{ name, value, start, end }],或者一些必须使用 props 的属性,变成了:
  8. * el.props = [{ name, value: true, start, end, dynamic }]
  9. */
  10. function processAttrs(el) {
  11. // list = [{ name, value, start, end }, ...]
  12. const list = el.attrsList
  13. let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  14. for (i = 0, l = list.length; i < l; i++) {
  15. // 属性名
  16. name = rawName = list[i].name
  17. // 属性值
  18. value = list[i].value
  19. if (dirRE.test(name)) {
  20. // 说明该属性是一个指令
  21. // 元素上存在指令,将元素标记动态元素
  22. // mark element as dynamic
  23. el.hasBindings = true
  24. // modifiers,在属性名上解析修饰符,比如 xx.lazy
  25. modifiers = parseModifiers(name.replace(dirRE, ''))
  26. // support .foo shorthand syntax for the .prop modifier
  27. if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
  28. // 为 .props 修饰符支持 .foo 速记写法
  29. (modifiers || (modifiers = {})).prop = true
  30. name = `.` + name.slice(1).replace(modifierRE, '')
  31. } else if (modifiers) {
  32. // 属性中的修饰符去掉,得到一个干净的属性名
  33. name = name.replace(modifierRE, '')
  34. }
  35. if (bindRE.test(name)) { // v-bind, <div :id="test"></div>
  36. // 处理 v-bind 指令属性,最后得到 el.attrs 或者 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...]
  37. // 属性名,比如:id
  38. name = name.replace(bindRE, '')
  39. // 属性值,比如:test
  40. value = parseFilters(value)
  41. // 是否为动态属性 <div :[id]="test"></div>
  42. isDynamic = dynamicArgRE.test(name)
  43. if (isDynamic) {
  44. // 如果是动态属性,则去掉属性两侧的方括号 []
  45. name = name.slice(1, -1)
  46. }
  47. // 提示,动态属性值不能为空字符串
  48. if (
  49. process.env.NODE_ENV !== 'production' &&
  50. value.trim().length === 0
  51. ) {
  52. warn(
  53. `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
  54. )
  55. }
  56. // 存在修饰符
  57. if (modifiers) {
  58. if (modifiers.prop && !isDynamic) {
  59. name = camelize(name)
  60. if (name === 'innerHtml') name = 'innerHTML'
  61. }
  62. if (modifiers.camel && !isDynamic) {
  63. name = camelize(name)
  64. }
  65. // 处理 sync 修饰符
  66. if (modifiers.sync) {
  67. syncGen = genAssignmentCode(value, `$event`)
  68. if (!isDynamic) {
  69. addHandler(
  70. el,
  71. `update:${camelize(name)}`,
  72. syncGen,
  73. null,
  74. false,
  75. warn,
  76. list[i]
  77. )
  78. if (hyphenate(name) !== camelize(name)) {
  79. addHandler(
  80. el,
  81. `update:${hyphenate(name)}`,
  82. syncGen,
  83. null,
  84. false,
  85. warn,
  86. list[i]
  87. )
  88. }
  89. } else {
  90. // handler w/ dynamic event name
  91. addHandler(
  92. el,
  93. `"update:"+(${name})`,
  94. syncGen,
  95. null,
  96. false,
  97. warn,
  98. list[i],
  99. true // dynamic
  100. )
  101. }
  102. }
  103. }
  104. if ((modifiers && modifiers.prop) || (
  105. !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
  106. )) {
  107. // 将属性对象添加到 el.props 数组中,表示这些属性必须通过 props 设置
  108. // el.props = [{ name, value, start, end, dynamic }, ...]
  109. addProp(el, name, value, list[i], isDynamic)
  110. } else {
  111. // 将属性添加到 el.attrs 数组或者 el.dynamicAttrs 数组
  112. addAttr(el, name, value, list[i], isDynamic)
  113. }
  114. } else if (onRE.test(name)) { // v-on, 处理事件,<div @click="test"></div>
  115. // 属性名,即事件名
  116. name = name.replace(onRE, '')
  117. // 是否为动态属性
  118. isDynamic = dynamicArgRE.test(name)
  119. if (isDynamic) {
  120. // 动态属性,则获取 [] 中的属性名
  121. name = name.slice(1, -1)
  122. }
  123. // 处理事件属性,将属性的信息添加到 el.events 或者 el.nativeEvents 对象上,格式:
  124. // el.events = [{ value, start, end, modifiers, dynamic }, ...]
  125. addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
  126. } else { // normal directives,其它的普通指令
  127. // 得到 el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
  128. name = name.replace(dirRE, '')
  129. // parse arg
  130. const argMatch = name.match(argRE)
  131. let arg = argMatch && argMatch[1]
  132. isDynamic = false
  133. if (arg) {
  134. name = name.slice(0, -(arg.length + 1))
  135. if (dynamicArgRE.test(arg)) {
  136. arg = arg.slice(1, -1)
  137. isDynamic = true
  138. }
  139. }
  140. addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
  141. if (process.env.NODE_ENV !== 'production' && name === 'model') {
  142. checkForAliasModel(el, value)
  143. }
  144. }
  145. } else {
  146. // 当前属性不是指令
  147. // literal attribute
  148. if (process.env.NODE_ENV !== 'production') {
  149. const res = parseText(value, delimiters)
  150. if (res) {
  151. warn(
  152. `${name}="${value}": ` +
  153. 'Interpolation inside attributes has been removed. ' +
  154. 'Use v-bind or the colon shorthand instead. For example, ' +
  155. 'instead of <div id="{{ val }}">, use <div :id="val">.',
  156. list[i]
  157. )
  158. }
  159. }
  160. // 将属性对象放到 el.attrs 数组中,el.attrs = [{ name, value, start, end }]
  161. addAttr(el, name, JSON.stringify(value), list[i])
  162. // #6887 firefox doesn't update muted state if set via attribute
  163. // even immediately after element creation
  164. if (!el.component &&
  165. name === 'muted' &&
  166. platformMustUseProp(el.tag, el.attrsMap.type, name)) {
  167. addProp(el, name, 'true', list[i])
  168. }
  169. }
  170. }
  171. }

addHandler

/src/compiler/helpers.js

  1. /**
  2. * 处理事件属性,将事件属性添加到 el.events 对象或者 el.nativeEvents 对象中,格式:
  3. * el.events[name] = [{ value, start, end, modifiers, dynamic }, ...]
  4. * 其中用了大量的篇幅在处理 name 属性带修饰符 (modifier) 的情况
  5. * @param {*} el ast 对象
  6. * @param {*} name 属性名,即事件名
  7. * @param {*} value 属性值,即事件回调函数名
  8. * @param {*} modifiers 修饰符
  9. * @param {*} important
  10. * @param {*} warn 日志
  11. * @param {*} range
  12. * @param {*} dynamic 属性名是否为动态属性
  13. */
  14. export function addHandler (
  15. el: ASTElement,
  16. name: string,
  17. value: string,
  18. modifiers: ?ASTModifiers,
  19. important?: boolean,
  20. warn?: ?Function,
  21. range?: Range,
  22. dynamic?: boolean
  23. ) {
  24. // modifiers 是一个对象,如果传递的参数为空,则给一个冻结的空对象
  25. modifiers = modifiers || emptyObject
  26. // 提示:prevent 和 passive 修饰符不能一起使用
  27. // warn prevent and passive modifier
  28. /* istanbul ignore if */
  29. if (
  30. process.env.NODE_ENV !== 'production' && warn &&
  31. modifiers.prevent && modifiers.passive
  32. ) {
  33. warn(
  34. 'passive and prevent can\'t be used together. ' +
  35. 'Passive handler can\'t prevent default event.',
  36. range
  37. )
  38. }
  39. // 标准化 click.right 和 click.middle,它们实际上不会被真正的触发,从技术讲他们是它们
  40. // 是特定于浏览器的,但至少目前位置只有浏览器才具有右键和中间键的点击
  41. // normalize click.right and click.middle since they don't actually fire
  42. // this is technically browser-specific, but at least for now browsers are
  43. // the only target envs that have right/middle clicks.
  44. if (modifiers.right) {
  45. // 右键
  46. if (dynamic) {
  47. // 动态属性
  48. name = `(${name})==='click'?'contextmenu':(${name})`
  49. } else if (name === 'click') {
  50. // 非动态属性,name = contextmenu
  51. name = 'contextmenu'
  52. // 删除修饰符中的 right 属性
  53. delete modifiers.right
  54. }
  55. } else if (modifiers.middle) {
  56. // 中间键
  57. if (dynamic) {
  58. // 动态属性,name => mouseup 或者 ${name}
  59. name = `(${name})==='click'?'mouseup':(${name})`
  60. } else if (name === 'click') {
  61. // 非动态属性,mouseup
  62. name = 'mouseup'
  63. }
  64. }
  65. /**
  66. * 处理 capture、once、passive 这三个修饰符,通过给 name 添加不同的标记来标记这些修饰符
  67. */
  68. // check capture modifier
  69. if (modifiers.capture) {
  70. delete modifiers.capture
  71. // 给带有 capture 修饰符的属性,加上 ! 标记
  72. name = prependModifierMarker('!', name, dynamic)
  73. }
  74. if (modifiers.once) {
  75. delete modifiers.once
  76. // once 修饰符加 ~ 标记
  77. name = prependModifierMarker('~', name, dynamic)
  78. }
  79. /* istanbul ignore if */
  80. if (modifiers.passive) {
  81. delete modifiers.passive
  82. // passive 修饰符加 & 标记
  83. name = prependModifierMarker('&', name, dynamic)
  84. }
  85. let events
  86. if (modifiers.native) {
  87. // native 修饰符, 监听组件根元素的原生事件,将事件信息存放到 el.nativeEvents 对象中
  88. delete modifiers.native
  89. events = el.nativeEvents || (el.nativeEvents = {})
  90. } else {
  91. events = el.events || (el.events = {})
  92. }
  93. const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range)
  94. if (modifiers !== emptyObject) {
  95. // 说明有修饰符,将修饰符对象放到 newHandler 对象上
  96. // { value, dynamic, start, end, modifiers }
  97. newHandler.modifiers = modifiers
  98. }
  99. // 将配置对象放到 events[name] = [newHander, handler, ...]
  100. const handlers = events[name]
  101. /* istanbul ignore if */
  102. if (Array.isArray(handlers)) {
  103. important ? handlers.unshift(newHandler) : handlers.push(newHandler)
  104. } else if (handlers) {
  105. events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
  106. } else {
  107. events[name] = newHandler
  108. }
  109. el.plain = false
  110. }

addIfCondition

/src/compiler/parser/index.js

  1. /**
  2. * 将传递进来的条件对象放进 el.ifConditions 数组中
  3. */
  4. export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
  5. if (!el.ifConditions) {
  6. el.ifConditions = []
  7. }
  8. el.ifConditions.push(condition)
  9. }

processPre

/src/compiler/parser/index.js

  1. /**
  2. * 如果元素上存在 v-pre 指令,则设置 el.pre = true
  3. */
  4. function processPre(el) {
  5. if (getAndRemoveAttr(el, 'v-pre') != null) {
  6. el.pre = true
  7. }
  8. }

processRawAttrs

/src/compiler/parser/index.js

  1. /**
  2. * 设置 el.attrs 数组对象,每个元素都是一个属性对象 { name: attrName, value: attrVal, start, end }
  3. */
  4. function processRawAttrs(el) {
  5. const list = el.attrsList
  6. const len = list.length
  7. if (len) {
  8. const attrs: Array<ASTAttr> = el.attrs = new Array(len)
  9. for (let i = 0; i < len; i++) {
  10. attrs[i] = {
  11. name: list[i].name,
  12. value: JSON.stringify(list[i].value)
  13. }
  14. if (list[i].start != null) {
  15. attrs[i].start = list[i].start
  16. attrs[i].end = list[i].end
  17. }
  18. }
  19. } else if (!el.pre) {
  20. // non root node in pre blocks with no attributes
  21. el.plain = true
  22. }
  23. }

processIf

/src/compiler/parser/index.js

  1. /**
  2. * 处理 v-if、v-else-if、v-else
  3. * 得到 el.if = "exp",el.elseif = exp, el.else = true
  4. * v-if 属性会额外在 el.ifConditions 数组中添加 { exp, block } 对象
  5. */
  6. function processIf(el) {
  7. // 获取 v-if 属性的值,比如 <div v-if="test"></div>
  8. const exp = getAndRemoveAttr(el, 'v-if')
  9. if (exp) {
  10. // el.if = "test"
  11. el.if = exp
  12. // 在 el.ifConditions 数组中添加 { exp, block }
  13. addIfCondition(el, {
  14. exp: exp,
  15. block: el
  16. })
  17. } else {
  18. // 处理 v-else,得到 el.else = true
  19. if (getAndRemoveAttr(el, 'v-else') != null) {
  20. el.else = true
  21. }
  22. // 处理 v-else-if,得到 el.elseif = exp
  23. const elseif = getAndRemoveAttr(el, 'v-else-if')
  24. if (elseif) {
  25. el.elseif = elseif
  26. }
  27. }
  28. }

processOnce

/src/compiler/parser/index.js

  1. /**
  2. * 处理 v-once 指令,得到 el.once = true
  3. * @param {*} el
  4. */
  5. function processOnce(el) {
  6. const once = getAndRemoveAttr(el, 'v-once')
  7. if (once != null) {
  8. el.once = true
  9. }
  10. }

checkRootConstraints

/src/compiler/parser/index.js

  1. /**
  2. * 检查根元素:
  3. * 不能使用 slot 和 template 标签作为组件的根元素
  4. * 不能在有状态组件的 根元素 上使用 v-for 指令,因为它会渲染出多个元素
  5. * @param {*} el
  6. */
  7. function checkRootConstraints(el) {
  8. // 不能使用 slot 和 template 标签作为组件的根元素
  9. if (el.tag === 'slot' || el.tag === 'template') {
  10. warnOnce(
  11. `Cannot use <${el.tag}> as component root element because it may ` +
  12. 'contain multiple nodes.',
  13. { start: el.start }
  14. )
  15. }
  16. // 不能在有状态组件的 根元素 上使用 v-for,因为它会渲染出多个元素
  17. if (el.attrsMap.hasOwnProperty('v-for')) {
  18. warnOnce(
  19. 'Cannot use v-for on stateful component root element because ' +
  20. 'it renders multiple elements.',
  21. el.rawAttrsMap['v-for']
  22. )
  23. }
  24. }

closeElement

/src/compiler/parser/index.js

  1. /**
  2. * 主要做了 3 件事:
  3. * 1、如果元素没有被处理过,即 el.processed 为 false,则调用 processElement 方法处理节点上的众多属性
  4. * 2、让自己和父元素产生关系,将自己放到父元素的 children 数组中,并设置自己的 parent 属性为 currentParent
  5. * 3、设置自己的子元素,将自己所有非插槽的子元素放到自己的 children 数组中
  6. */
  7. function closeElement(element) {
  8. // 移除节点末尾的空格,当前 pre 标签内的元素除外
  9. trimEndingWhitespace(element)
  10. // 当前元素不再 pre 节点内,并且也没有被处理过
  11. if (!inVPre && !element.processed) {
  12. // 分别处理元素节点的 key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、其它指令和一些原生属性
  13. element = processElement(element, options)
  14. }
  15. // 处理根节点上存在 v-if、v-else-if、v-else 指令的情况
  16. // 如果根节点存在 v-if 指令,则必须还提供一个具有 v-else-if 或者 v-else 的同级别节点,防止根元素不存在
  17. // tree management
  18. if (!stack.length && element !== root) {
  19. // allow root elements with v-if, v-else-if and v-else
  20. if (root.if && (element.elseif || element.else)) {
  21. if (process.env.NODE_ENV !== 'production') {
  22. // 检查根元素
  23. checkRootConstraints(element)
  24. }
  25. // 给根元素设置 ifConditions 属性,root.ifConditions = [{ exp: element.elseif, block: element }, ...]
  26. addIfCondition(root, {
  27. exp: element.elseif,
  28. block: element
  29. })
  30. } else if (process.env.NODE_ENV !== 'production') {
  31. // 提示,表示不应该在 根元素 上只使用 v-if,应该将 v-if、v-else-if 一起使用,保证组件只有一个根元素
  32. warnOnce(
  33. `Component template should contain exactly one root element. ` +
  34. `If you are using v-if on multiple elements, ` +
  35. `use v-else-if to chain them instead.`,
  36. { start: element.start }
  37. )
  38. }
  39. }
  40. // 让自己和父元素产生关系
  41. // 将自己放到父元素的 children 数组中,然后设置自己的 parent 属性为 currentParent
  42. if (currentParent && !element.forbidden) {
  43. if (element.elseif || element.else) {
  44. processIfConditions(element, currentParent)
  45. } else {
  46. if (element.slotScope) {
  47. // scoped slot
  48. // keep it in the children list so that v-else(-if) conditions can
  49. // find it as the prev node.
  50. const name = element.slotTarget || '"default"'
  51. ; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
  52. }
  53. currentParent.children.push(element)
  54. element.parent = currentParent
  55. }
  56. }
  57. // 设置自己的子元素
  58. // 将自己的所有非插槽的子元素设置到 element.children 数组中
  59. // final children cleanup
  60. // filter out scoped slots
  61. element.children = element.children.filter(c => !(c: any).slotScope)
  62. // remove trailing whitespace node again
  63. trimEndingWhitespace(element)
  64. // check pre state
  65. if (element.pre) {
  66. inVPre = false
  67. }
  68. if (platformIsPreTag(element.tag)) {
  69. inPre = false
  70. }
  71. // 分别为 element 执行 model、class、style 三个模块的 postTransform 方法
  72. // 但是 web 平台没有提供该方法
  73. // apply post-transforms
  74. for (let i = 0; i < postTransforms.length; i++) {
  75. postTransforms[i](element, options)
  76. }
  77. }

trimEndingWhitespace

/src/compiler/parser/index.js

  1. /**
  2. * 删除元素中空白的文本节点,比如:<div> </div>,删除 div 元素中的空白节点,将其从元素的 children 属性中移出去
  3. */
  4. function trimEndingWhitespace(el) {
  5. if (!inPre) {
  6. let lastNode
  7. while (
  8. (lastNode = el.children[el.children.length - 1]) &&
  9. lastNode.type === 3 &&
  10. lastNode.text === ' '
  11. ) {
  12. el.children.pop()
  13. }
  14. }
  15. }

processIfConditions

/src/compiler/parser/index.js

  1. function processIfConditions(el, parent) {
  2. // 找到 parent.children 中的最后一个元素节点
  3. const prev = findPrevElement(parent.children)
  4. if (prev && prev.if) {
  5. addIfCondition(prev, {
  6. exp: el.elseif,
  7. block: el
  8. })
  9. } else if (process.env.NODE_ENV !== 'production') {
  10. warn(
  11. `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
  12. `used on element <${el.tag}> without corresponding v-if.`,
  13. el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
  14. )
  15. }
  16. }

findPrevElement

/src/compiler/parser/index.js

  1. /**
  2. * 找到 children 中的最后一个元素节点
  3. */
  4. function findPrevElement(children: Array<any>): ASTElement | void {
  5. let i = children.length
  6. while (i--) {
  7. if (children[i].type === 1) {
  8. return children[i]
  9. } else {
  10. if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
  11. warn(
  12. `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
  13. `will be ignored.`,
  14. children[i]
  15. )
  16. }
  17. children.pop()
  18. }
  19. }
  20. }

帮助

到这里编译器的解析部分就结束了,相信很多人看的是云里雾里的,即使多看几遍可能也没有那么清晰。

不要着急,这个很正常,编译器这块儿的代码量确实是比较大。但是内容本身其实不复杂,复杂的是它要处理东西实在是太多了,这才导致这部分的代码量巨大,相对应的,就会产生比较难的感觉。确实不简单,至少我觉得它是整个框架最复杂最难的地方了。

对照着视频和文章大家可以多看几遍,不明白的地方写一些示例代码辅助调试,编写详细的注释。还是那句话,书读百遍,其义自现。

阅读的过程中,大家需要抓住编译器解析部分的本质:将类 HTML 字符串模版解析成 AST 对象。

所以这么多代码都在做一件事情,就是解析字符串模版,将整个模版用 AST 对象来表示和记录。所以,大家阅读的时候,可以将解析过程中生成的 AST 对象记录下来,帮助阅读和理解,这样在读完以后不至于那么迷茫,也有助于大家理解。

这是我在阅读的时候的一个简单记录:

  1. const element = {
  2. type: 1,
  3. tag,
  4. attrsList: [{ name: attrName, value: attrVal, start, end }],
  5. attrsMap: { attrName: attrVal, },
  6. rawAttrsMap: { attrName: attrVal, type: checkbox },
  7. // v-if
  8. ifConditions: [{ exp, block }],
  9. // v-for
  10. for: iterator,
  11. alias: 别名,
  12. // :key
  13. key: xx,
  14. // ref
  15. ref: xx,
  16. refInFor: boolean,
  17. // 插槽
  18. slotTarget: slotName,
  19. slotTargetDynamic: boolean,
  20. slotScope: 作用域插槽的表达式,
  21. scopeSlot: {
  22. name: {
  23. slotTarget: slotName,
  24. slotTargetDynamic: boolean,
  25. children: {
  26. parent: container,
  27. otherProperty,
  28. }
  29. },
  30. slotScope: 作用域插槽的表达式,
  31. },
  32. slotName: xx,
  33. // 动态组件
  34. component: compName,
  35. inlineTemplate: boolean,
  36. // class
  37. staticClass: className,
  38. classBinding: xx,
  39. // style
  40. staticStyle: xx,
  41. styleBinding: xx,
  42. // attr
  43. hasBindings: boolean,
  44. nativeEvents: {同 evetns},
  45. events: {
  46. name: [{ value, dynamic, start, end, modifiers }]
  47. },
  48. props: [{ name, value, dynamic, start, end }],
  49. dynamicAttrs: [同 attrs],
  50. attrs: [{ name, value, dynamic, start, end }],
  51. directives: [{ name, rawName, value, arg, isDynamicArg, modifiers, start, end }],
  52. // v-pre
  53. pre: true,
  54. // v-once
  55. once: true,
  56. parent,
  57. children: [],
  58. plain: boolean,
  59. }

总结

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

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

    • 将组件的 html 模版解析成 AST 对象

    • 优化,遍历 AST,为每个节点做静态标记,标记其是否为静态节点,然后进一步标记出静态根节点,这样在后续更新的过程中就可以跳过这些静态节点了;标记静态根用于生成渲染函数阶段,生成静态根节点的渲染函数

    • 从 AST 生成运行时的渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,里面存放了所有的静态节点的渲染函数


  • 面试官 问:详细说一说编译器的解析过程,它是怎么将 html 字符串模版变成 AST 对象的?

    • 遍历 HTML 模版字符串,通过正则表达式匹配 "<"

    • 跳过某些不需要处理的标签,比如:注释标签、条件注释标签、Doctype。

      备注:整个解析过程的核心是处理开始标签和结束标签

    • 解析开始标签

      • 得到一个对象,包括 标签名(tagName)、所有的属性(attrs)、标签在 html 模版字符串中的索引位置

      • 进一步处理上一步得到的 attrs 属性,将其变成 [{ name: attrName, value: attrVal, start: xx, end: xx }, ...] 的形式

      • 通过标签名、属性对象和当前元素的父元素生成 AST 对象,其实就是一个 普通的 JS 对象,通过 key、value 的形式记录了该元素的一些信息

      • 接下来进一步处理开始标签上的一些指令,比如 v-pre、v-for、v-if、v-once,并将处理结果放到 AST 对象上

      • 处理结束将 ast 对象存放到 stack 数组

      • 处理完成后会截断 html 字符串,将已经处理掉的字符串截掉

    • 解析闭合标签

      • 如果匹配到结束标签,就从 stack 数组中拿出最后一个元素,它和当前匹配到的结束标签是一对。

      • 再次处理开始标签上的属性,这些属性和前面处理的不一样,比如:key、ref、scopedSlot、样式等,并将处理结果放到元素的 AST 对象上

        备注 视频中说这块儿有误,回头看了下,没有问题,不需要改,确实是这样

      • 然后将当前元素和父元素产生联系,给当前元素的 ast 对象设置 parent 属性,然后将自己放到父元素的 ast 对象的 children 数组中

    • 最后遍历完整个 html 模版字符串以后,返回 ast 对象

链接

感谢各位的:关注点赞收藏评论,我们下期见。


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

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

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

Vue 源码解读(8)—— 编译器 之 解析(下)的更多相关文章

  1. Vue 源码解读(8)—— 编译器 之 解析(上)

    特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了上下两篇,所以在阅读本篇文章时请同时打开 Vue 源码解读(8)-- 编译器 之 解析(下)一起阅读. 前言 V ...

  2. Vue 源码解读(9)—— 编译器 之 优化

    前言 上一篇文章 Vue 源码解读(8)-- 编译器 之 解析 详细详解了编译器的第一部分,如何将 html 模版字符串编译成 AST.今天带来编译器的第二部分,优化 AST,也是大家常说的静态标记. ...

  3. Vue 源码解读(10)—— 编译器 之 生成渲染函数

    前言 这篇文章是 Vue 编译器的最后一部分,前两部分分别是:Vue 源码解读(8)-- 编译器 之 解析.Vue 源码解读(9)-- 编译器 之 优化. 从 HTML 模版字符串开始,解析所有标签以 ...

  4. Vue 源码解读(2)—— Vue 初始化过程

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  5. Vue 源码解读(11)—— render helper

    前言 上一篇文章 Vue 源码解读(10)-- 编译器 之 生成渲染函数 最后讲到组件更新时,需要先执行编译器生成的渲染函数得到组件的 vnode. 渲染函数之所以能生成 vnode 是通过其中的 _ ...

  6. Vue 源码解读(1)—— 前言

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  7. Vue 源码解读(5)—— 全局 API

    目标 深入理解以下全局 API 的实现原理. Vue.use Vue.mixin Vue.component Vue.filter Vue.directive Vue.extend Vue.set V ...

  8. Vue 源码解读(6)—— 实例方法

    前言 上一篇文章 Vue 源码解读(5)-- 全局 API 详细介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会详细介绍各个实例方法的实现原理. 目标 深入理解以下实例方法的实现原理. v ...

  9. Vue 源码解读(3)—— 响应式原理

    前言 上一篇文章 Vue 源码解读(2)-- Vue 初始化过程 详细讲解了 Vue 的初始化过程,明白了 new Vue(options) 都做了什么,其中关于 数据响应式 的实现用一句话简单的带过 ...

随机推荐

  1. Centos7下,Docker的安装与使用

    一.Docker Install 1.卸载旧的版本 yum remove docker \ docker-client \ docker-client-latest \ docker-common \ ...

  2. zabbix安装 报错 socket '/var/lib/mysql/mysql.sock' (13)]

    安装界面提示: Error connecting to database: Can't connect to local MySQL server through socket '/var/lib/m ...

  3. 浅谈kali : arpspoof工具原理

    Arpspoof工具 介绍 arpspoof是一个通过ARP协议伪造数据包实现中间人攻击的kali工具. 中间人攻击虽然古老,但仍处于受到黑客攻击的危险中,可能会严重导致危害服务器和用户.仍然有很多变 ...

  4. ZooKeeper 授权访问

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  5. 分页方法需要参数(当前页数,总数据量,拿到query的值)

    class Pagination: def __init__(self, page_num, all_count ,params=None,per_num=10,max_show=11): " ...

  6. ApacheCN C# 译文集 20211124 更新

    C# 代码整洁指南 零.前言 一.C# 代码标准和原则 二.代码审查--过程和重要性 三.类.对象和数据结构 四.编写整洁的函数 五.异常处理 六.单元测试 七.端到端系统测试 八.线程和并发 九.设 ...

  7. v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(共享内存篇) | 进程间最快通讯方式 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) | 同样 ...

  8. 「JOI 2015 Final」城墙

    「JOI 2015 Final」城墙 复杂度默认\(m=n\) 暴力 对于点\((i,j)\),记录\(ld[i][j]=min(向下延伸的长度,向右延伸的长度)\),\(rd[i][j]=min(向 ...

  9. DP 专练

    A - 跳蚤电话 观察性质,可以发现每次连边的点一定是有祖先关系的,可以直接挂上去一个,也可以是在中间边上插入一个点. 所以我很自然的想到去计算树上的点的加入顺序,因为一但加入顺序确定,每一次的操作也 ...

  10. JVM学习八-(复习)年轻代、老年代、永久代

    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old).新生代 ...