首页>>前端>>Vue->源码分析:Vue 编译(compile)核心流程之parse(中)

源码分析:Vue 编译(compile)核心流程之parse(中)

时间:2023-11-30 本站 点击:0

   上一节我们已经介绍了template解析成match对象的一个过程,不清楚可以点击这里,本节我们来分析match对象生成ast树的过程。

Vue 编译(compile)核心流程之parse(AST树的生成)

template模板字符串中的开始标签、结束标签、文本字符串等的大致解析过程在上一节我们已经介绍了,这一节我们来继续分析。

当处理开始标签的时候,其中会执行到handleStartTag函数,这个函数的最后一步会执行options.start函数,接下来我们分析下这个函数:

function handleStartTag (match) {  ......  // 调用options中的start函数  if (options.start) {    options.start(tagName, attrs, unary, match.start, match.end)  }}

start函数

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)  }  // 生成ast树的核心函数,返回ast对象 { type: 1, tag, attrsList, attrsMap, parent, children}  let element: ASTElement = createASTElement(tag, attrs, currentParent)  if (ns) {    element.ns = ns  }  // 如果是script、stype、text/javascript等标签且非服务端渲染,则报错  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 执行平台相关的preTransforms函数  for (let i = 0; i < preTransforms.length; i++) {    element = preTransforms[i](element, options) || element  }  // 对ast对象做一个丰富  // v-pre相关,这个指定对于没有其他指令的标签用处较大,跳过大量没有指令的节点会加快编译。  if (!inVPre) {    // 拿到v-pre的值,给AST element添加pre属性,并且在attrs数组及attrsMap中删掉该指令    processPre(element)    if (element.pre) {      // 解析到了v-pre指令,inVPre赋值为true      inVPre = true    }  }  // 如果tag是pre标签  if (platformIsPreTag(element.tag)) {    // inPre赋为true    inPre = true  }  // inVPre为true的时候,执行processRawAttrs函数,就不需要编译分析其他的指令了  if (inVPre) {    processRawAttrs(element)  } else if (!element.processed) {    // 没有v-pre,解析attr中存储的指令    // structural directives    // 解析For指令    processFor(element)    // 解析If指令    processIf(element)    // 解析Once指令    processOnce(element)    // element-scope stuff    // 解析其他的指令    processElement(element, options)  }  function checkRootConstraints (el) {    if (process.env.NODE_ENV !== 'production') {      if (el.tag === 'slot' || el.tag === 'template') {        warnOnce(          `Cannot use <${el.tag}> as component root element because it may ` +          'contain multiple nodes.'        )      }      if (el.attrsMap.hasOwnProperty('v-for')) {        warnOnce(          'Cannot use v-for on stateful component root element because ' +          'it renders multiple elements.'        )      }    }  }  // tree management  // ast树管理  if (!root) {    root = element    // 检查根节点    checkRootConstraints(root)  } else if (!stack.length) {    // 不止一个根节点,报错    // allow root elements with v-if, v-else-if and v-else    if (root.if && (element.elseif || element.else)) {      checkRootConstraints(element)      addIfCondition(root, {        exp: element.elseif,        block: element      })    } else if (process.env.NODE_ENV !== 'production') {      warnOnce(        `Component template should contain exactly one root element. ` +        `If you are using v-if on multiple elements, ` +        `use v-else-if to chain them instead.`      )    }  }  // 对父子节点的关系进行管理,生成规范的AST树  if (currentParent && !element.forbidden) {    if (element.elseif || element.else) { // elseif的逻辑分析      processIfConditions(element, currentParent)    } else if (element.slotScope) { // scoped slot slot的相关逻辑分析      currentParent.plain = false      const name = element.slotTarget || '"default"'      ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element    } else { // 管理父子节点关系      currentParent.children.push(element)      element.parent = currentParent    }  }  // 非一元标签,需要执行推栈入栈操作,管理ast树  if (!unary) {    currentParent = element    stack.push(element)  } else {    // 关闭标签    closeElement(element)  }},

start函数主要的逻辑分成两个部分,第一部分是创建AST树,解析属性,对AST树进行扩展,第二部分是对AST树进行管理;

1.创建AST树并解析属性

创建AST树

export function createASTElement (  tag: string,  attrs: Array<Attr>,  parent: ASTElement | void): ASTElement {  return {    type: 1,    tag,    attrsList: attrs,    attrsMap: makeAttrsMap(attrs),    parent,    children: []  }}

function makeAttrsMap (attrs: Array<Object>): Object {  const map = {}  for (let i = 0, l = attrs.length; i < l; i++) {    if (      process.env.NODE_ENV !== 'production' &&      map[attrs[i].name] && !isIE && !isEdge    ) {      warn('duplicate attribute: ' + attrs[i].name)    }    map[attrs[i].name] = attrs[i].value  }  return map}

这个地方会返回一个原始的AST树对象,makeAttrsMap函数会遍历attr数组里面的对象属性,解析成为一个map对象键值对的形式,这样会方便后续对属性的查找取值操作。

解析指令

生成了基本的AST树后,会对标签的属性进行解析,这里我们分析下部分指令的解析:

v-if指令

function processIf (el) {  // getAndRemoveAttr函数会取出AST element对象中的v-if指令作为exp,  // 并移除el对象的attr数组和attrMap对象中的v-if指令  // 如v-if="isTrue"会被解析成exp会赋值为isTrue  const exp = getAndRemoveAttr(el, 'v-if')  // 有表达式的情况  if (exp) {    // 在AST element对象上添加if属性    el.if = exp    添加ifConditions属性数组,将第二个参数push进该数组    addIfCondition(el, {      exp: exp,      block: el    })  } else {    // 如果是v-if不存在,设置v-else指令    if (getAndRemoveAttr(el, 'v-else') != null) {      el.else = true    }    // 如果是v-if不存在,设置v-else-if指令    const elseif = getAndRemoveAttr(el, 'v-else-if')    if (elseif) {      el.elseif = elseif    }  }}

export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {  // 给AST element添加ifConditions属性,该属性为数组  if (!el.ifConditions) {    el.ifConditions = []  }  el.ifConditions.push(condition)}

其他指令

export function processElement (element: ASTElement, options: CompilerOptions) {  // 解析Key指令  processKey(element)  // determine whether this is a plain element after  // removing structural attributes  element.plain = !element.key && !element.attrsList.length  // 解析Ref指令  processRef(element)  // 解析Slot指令  processSlot(element)  // 解析Component指令  processComponent(element)  for (let i = 0; i < transforms.length; i++) {    element = transforms[i](element, options) || element  }  // 解析剩余的其他指令如event model等  processAttrs(element)}

2.AST树管理

// tree management// ast树管理if (!root) {  root = element  // 检查根节点  checkRootConstraints(root)} else if (!stack.length) {  // 不止一个根节点,报错  // allow root elements with v-if, v-else-if and v-else  if (root.if && (element.elseif || element.else)) {    checkRootConstraints(element)    addIfCondition(root, {      exp: element.elseif,      block: element    })  } else if (process.env.NODE_ENV !== 'production') {    warnOnce(      `Component template should contain exactly one root element. ` +      `If you are using v-if on multiple elements, ` +      `use v-else-if to chain them instead.`    )  }}if (currentParent && !element.forbidden) {  if (element.elseif || element.else) {    processIfConditions(element, currentParent)  } else if (element.slotScope) { // scoped slot    currentParent.plain = false    const name = element.slotTarget || '"default"'    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element  } else {    currentParent.children.push(element)    element.parent = currentParent  }}if (!unary) {  currentParent = element  stack.push(element)} else {  // 关闭标签  closeElement(element)}

AST树管理的主要作用就是去维护AST树对象的父子关系,并对根节点做一些检测,比如不能出现重复的根节点情况等,此外还会对一些指令进行解析。

总结

本节我们分析了处理开始标签中start函数的整个解析流程,大致可以分为三步:

1.生成初始的AST对象;

2.将我们扫描开始标签生成的match对象中attr数组作进一步的解析,针对不同的指令属性,解析生成不同的属性扩展到AST树对象上;

3.对生成的AST对象进行管理,维护AST树对象的父子关系;

下一节我们继续分析parse过程的其他流程,点击这里去往下一节。

原文:https://juejin.cn/post/7095294495746424840


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/3734.html