浅曦Vue源码-19-挂载阶段-$mount(8)

573 阅读4分钟

「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。

一、前情回顾 & 背景

上文详细讨论了创建 AST 的方法 createASTElement 方法,以及来自 options.modulespreTransforms 变量所代表的 preTransfromNode 方法;

  1. createASTElement 方法创建 type1,即元素的 AST 节点对象,包含 parentchildren 等用于组织节点间关系的属性;
  2. preTransformsNode 方法就是预处理带有 v-model 且动态绑定了 type 属性的 input 标签,目的是解决无论 type 绑定何种值,最后都能渲染除一个符合预期的 input 元素。

原本上一篇就想讲完所有的 options.start 方法的所有工具方法,这一番操作后,我发现了写了一通,只写完了 preTransformsNode 方法。。。本以为能很快填完这个大坑,结果没想到这个坑是越挖越大了,今天继续填坑,今天接着写 options.start 的工具方法具体的逻辑

二、processPre 方法

方法位置:src/compiler/parser/index.js -> function processPre

方法参数:elementast 节点对象

方法作用:从 element 的 属性对象上获取 v-pre 属性值,如果值不为 null 就说明当前 ast 对象上存在 v-pre 指令,为 element.pre 设置为 true

function processPre (el) {
  if (getAndRemoveAttr(el, 'v-pre') != null) {
    el.pre = true
  }
}

三、getAndRemoveAttr

方法位置:src/compiler/helpers.js

方法参数:

  1. elAST 节点对象
  2. name:要从 el 上获取的属性名
  3. removeFromMap:是否从 map 中移除

方法作用:如果属性 nameel.attrsMap 中存在且值不为 null,获取这个 name 对应的值,即 el.attrsMap[name],然后从 el.attrsList 数组中移除掉 name 对应的一项,并且如果 removeFromMaptrue 时,还要从 el.attrsMap 中删除掉 name

注意,一般情况下,即 removeFromMap 不为 true,此时只会从 el.attrsList 数组中删除,之所以这么做是为了防止 processAttrs 方法处理,是因为 attrsMapast 生成代码的时候还要用到;

export function getAndRemoveAttr (
  el: ASTElement,
  name: string,
  removeFromMap?: boolean
): ?string {
  let val
  // 将属性 name 从 el.attrsList 中移除
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1) // splice 从 attrList 中删除
        break
      }
    }
  }

  // 如果 removeFromMap 为 true,则从 attrsMap 中删除 name
  if (removeFromMap) {
    delete el.attrsMap[name]
  }

  // 返回属性值
  return val
}

四、platformIsPreTag

方法位置:该方法是通过 createCompiler(baseOptions) 时随着 baseOptions 传入的,baseOptions 中 isPreTag 方法来自 src/platforms/web/util/element.js -> function isPreTag

方法参数:tag,标签名

方法作用:判断 tag 是否为 pre,即判断当前标签是否是 <pre></pre> 标签

export const isPreTag = (tag: ?string): boolean => tag === 'pre'

五、processRawAttrs

方法位置:src/compiler/parser/index.js -> functions processRawAttrs

方法参数:elast 节点对象

方法作用:将 el.attrsList 复制到 el.attrs 数组上。为什么这么做呢?

这是因为 inVPretrue 时,说明在当前节点上使用了 v-pre 指令,而 v-pre 指令的作用就是将当前节点及其子节点都变为静态节点,即便在使用 v-pre 的节点中使用 {{}} 这种语法也不会被处理。

el.attrs 数组中的每一项都是 { name: attrName, value: attrVal, start, end } 这种形式的。此外 el.attrs 上的属性时静态属性,数据更新时这部分属性会被忽略。

  • processRawAttrs 调用示意
// 示意 processRawAttrs 方法的调用位置,省略原始的 parse 和 parseHTML 方法中的很多细节内容
export functions parse () {
  parseHTML(template, {
    // options.start
    start () {
       // .... 省略
       if (inVPre) 
         // inVPre 为 true 时说明当前节点上有 v-pre 指令
         // processRawAttrs 方法调用示例
         processRawAttrs(element)
        }
    }
  })
}
  • processRawAttrs 方法
function processRawAttrs (el) {
  const list = el.attrsList // 原始的 attrsList 属性
  const len = list.length
  if (len) {
    const attrs: Array<ASTAttr> = el.attrs = new Array(len)
    for (let i = 0; i < len; i++) {
      attrs[i] = {
        name: list[i].name,
        value: JSON.stringify(list[i].value)
      }
      if (list[i].start != null) {
        attrs[i].start = list[i].start
        attrs[i].end = list[i].end
      }
    }
  } else if (!el.pre) {
    // non root node in pre blocks with no attributes
    el.plain = true
  }
}

六、processFor

方法位置:src/compiler/parser/index.js -> functions processFor

方法参数:element,ast 节点对象

方法作用:处理节点上的 v-for 指令,将解析所得到的结果保存到 element 上。v-for 的解析结果形如: { for: 可迭代的对象, alias: 可迭代对象条目名 }

以下代码为例:

<span v-for="item in someArr" :key="index">{{item}}</span>

<!-- someArr = ['A', 'B', 'C', 'D'] -->

可迭代对象就是 someArr,可迭代对象条目名就是 item;

export function processFor (el: ASTElement) {
  let exp
  // 获取 el 上的 v-for 属性的值,v-for 虽然是指令,但是在 ast 这里就是个行内属性
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    // 解析 v-for 的表达式的值:item in someArr ,
    // 得到 { for: 可迭代对象, alias: 别名 }
    const res = parseFor(exp)
    if (res) {
      // 将 res 对象上的属性复制到 el 对象上,
      // el.for = "someArr"
      // el.alias = "item" 
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`,
        el.rawAttrsMap['v-for']
      )
    }
  }
}

6.1 parseFor

方法位置:src/compiler/parser/index.js -> parseFor

方法参数:ast 对象上 v-for 指令对应的表达式,例如 "item in someArr"

方法作用:用各种正则把 item in someArr 这种 v-for 指令的表达式解析出来,得到可迭代对象 someArr 和可迭代对象的迭代条目 item

export function parseFor (exp: string): ?ForParseResult {
  // exp 就是 v-for="item in someArr" 中 v-for 的值
  
  // inMatch: ["item in someArr", "item", "someArr"]
  const inMatch = exp.match(forAliasRE)
  if (!inMatch) return
  const res = {}
  res.for = inMatch[2].trim()
  const alias = inMatch[1].trim().replace(stripParensRE, '')
  const iteratorMatch = alias.match(forIteratorRE)
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, '').trim()
    res.iterator1 = iteratorMatch[1].trim()
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
}

如下图就是 parseFor(exp) 得到的结果:

image.png

七、总结

本文接着前文讨论了 options.start 的工具方法:

  1. processPre:判断元素上是否有 v-pre 指令,有的话设置 el.pretrue

  2. getAndRemoveAttr:从 el.attrsMap 中获取指定属性名的属性值,然后从 el.attrsList 数组中删除这个属性,如果传递了 removeFromMap 也要从 attrsMap 中删除掉这个属性;

  3. platformIsPreTag:判断是否为 pre 标签;

  4. processRawAttr:将元素上的属性复制到 el.attrs 数组中,el.attrs 数组的属性均为静态属性,当数据发生变化时忽略这些属性

  5. processFor: 处理 v-for 指令,得到 { for: 可迭代对象, alias: 可迭代对象条目名 }