Skip to content
On this page

处理匹配的标签

当之前的while循环匹配到对应的开始标签、结束标签、文本、注释等节点时,调用传入的各回调函数生成对应的 AST节点

回调函数在 parser执行编译 的时候传入

start实现

js
// src/compiler/parser/index.js
start(tag, attrs, unary, start, end) {
  // attrs = [{ name, value }]
  ...
  let element = createASTElement(tag, attrs, currentParent)
  // 创建AST元素 => js Object
  ...
  if (isForbiddenTag(element) && !isServerRendering()) {
    // tag 不能style/script
  }
  for (let i = 0; i < preTransforms.length; i++) {
    // 平台相关的预处理函数
    element = preTransform[i](element, options) || element
  }
  if (!inVPre) {
    processPre(element)
    // v-pre指令会跳过编译成render的部分
    // v-pre赋值element.pre = true
    if (element.pre) {
      inVPre = true;
    }
  }
  if (platformIsPreTag(element.tag)) {
    inPre = true // pre标签(不同平台的表现形式不同)
  }
  if (inVPre) {
    precessRawAttrs(element)
  } else if (!element.processed) {
    processFor(element) // v-for
    processIf(element) // v-if
    processOnce(element) // v-once
  }
  if (!root) {
    root = element
    // 组件创建时的根节点
  }
  if (!unary) {
    currentParent = element // 不是自闭合标签
    stack.push(element) // 同样以一个栈结构数据维护
  } else {
    closeElement(element)
  }
}

processRawAttrs

js
function processRawAttrs (el) {
  // 作用是将element的属性value序列化成字符串,防止解析成模板
  const list = el.attrsList
  const len = list.lenght
  if (len) {
    const attrs = el.attrs = new Array(len)
    for (let i = 0; i < len; i++) {
      attrs[i] = {
        name: list[i].name,
        value: JSON.stringify(list[i].value) // 序列化value
      }
      if (list[i].start != null) {
        attrs[i].sart = list[i].start
        attrs.end = list[i].end
      }
    }
  } else if (!el.pre) {
    el.plain = true
  }
  // 这里传入的是AST节点的引用,所以修改都会反馈到外面 start 函数的AST节点
}

processFor

js
function processFor (el) {
  let exp
  if ((exp = getAndRemoveAttrs(el, 'v-for'))) {
    // exp: "(item, index) in list" 也就是v-for里面的表达式
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    } else if () {
      // 错误处理
    }
  }
}

function parseFor (exp) {
  const inMatch = exp.match(forAliasRE)
  //const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
  // 匹配 for 或者 of 循环
  if (!inMatch) return
  const res = {}
  res.for = inMatch[2].trim() // list
  // (item, index) => item, index
  const alias = inMatch[1].trim().replace(stripParensRE, '') // item, index
  const iteratorMatch = alias.match(forIteratorRE) // [',index', ' index', ..]
  // const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
  // 这个iteratorRE 匹配的是 ,index 以及之后的字符串
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, '')
    res.iterator1 = iteratorMatch[1].trim();
    if (iteratorMatch[2]) {
      // 对象的循环
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
  // { for: 'list', alias: 'item', iterator1: 'index' }
}

processIf

js
function processIf (el) {
  const exp  getAndRemoveAttr(el, 'v-if')
  if (exp) {
    exp.if = exp
    addIfCondition(el, {
      exp: exp,
      block: el // block其实就是生成的AST节点
    })
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true
    }
    const elseif = getAndRemoveAttr(el, 'v-else-if')
    if (elseif) {
      el-elseif = elseif
    }
  }
}

function addIfCondition(el, condition) {
  if (!el.ifConditions) {
    el.ifConditions = []
  }
  // 收集所有的if 条件到el的条件数组
  el.ifConditions.push(condition)
}

processOnce

js
function processOnce (el) {
  // v-once只会渲染一次,随后视为静态内容,用于优化更新性能
  const once = getAndRemoveAttr(el, 'v-once')
  if (once != null) {
    el.once = true
  }
}

小结

上面的代码,解释了AST开始节点的二次加工过程,根据编译的结果,先后进行v-for / v-if / v-once的处理

end 实现

js
end (tag, start, end) {
  const element = stack[stack.length - 1]
  stack.length -= 1 // 更新栈尾
  currentParent = stack[stack.length - 1]
  closeElement(element) // 用之前栈尾的开始节点做结束处理
}

function closeElement(element) {
  ...
  if (!inVPre && !element.processed) {
    // 非 v-pre 的节点要先处理
    // 这里的处理之后解析
    element = processElement(element, options)
  }
  if (!stack.length && element !== root) {
    // 此时闭合的节点已经是组件的根节点之一,也就是存在组件的根节点会有v-if/v-else的存在
    if (root.if && (element.elseif || element.else)) {
      addIfCondition(root, {
        exp: element.elseif,
        block: element,
      })
    } else if () { /** 错误处理 **/ }
  }
  if (currentParent && !element.forbidden) {
    // currentParent就是栈内element的前一个入栈元素,也就是树形结构中的父节点
    if (element.elseif || element.else) {
      processIfConditions(element, currentParent)
    } else {
      if (element.slotScoped) {
        // element是作用域插槽内容
        const name = element.slotTarget || '"default"'
        // 将插槽slot赋值到父节点的插槽map对象中
        ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
      }
      currentParent.children.push(element)
      element.parent = currentParent
    }
  }
  // element设置children,slot的话则是设置默认内容
  element.children = element.children.filter(c => !c.slotScope)
  ...
  // 重置 pre 的标识位
  if (element.pre) {
    inVPre = false
  }
  if (platformIsPreTag(element.tag)) {
    inPre = false
  }
}

processElement

这是几乎每个AST节点都会执行的逻辑,v-pre的除外

主要是处理节点中的一些属性

js
function processElement (element, options) {
  // 处理element中的key: el.key = index
  // 检测是否有template中的key属性,或者transition-group的key属性
  processKey(element)
  element.plain = (
    // 判断节点的复杂性
    !element.key && !element.scopedSlots && !element.attrsList.length
  )
  // 处理ref以及for循环的ref
  processRef(element)
  // 处理 slot 的属性
  processSlotContent(element)
  // 处理 slot 标签节点 <slot>
  processSlotOutlet(element)
  // component is属性处理
  processComponent(element)
  ...
  // 处理element的属性列表
  processAttrs(element)
  return element
  // 最终返回处理过后的节点
}

processSlotContent

js
function processSlotContent(el) {
  let slotScope
  // 在传入之前处理插槽slot节点
  if (el.tag === 'template') {
    // <template scope="xxx">
    // <template slot-scope="xxx">
		slotScope = getAndRemoveAttr(el, 'scope')
    el.slotScopd = slotScope || getAndRemoveAttr(el, 'slot-scope')
  } else if (slotScope = getAndRemoveAttr(el, 'slot-scope')) {
    // <div slot-scope="xxx">
    el.slotScope = slotScope
  }
  // 上述代码获取作用域插槽的内容
  
  // <div slot='xxx'>
  // <template slot='xxx'>
  // 获取插槽插入的位置,最终存到 element 的 attrs 列表中
  const slotTarget = getBindingAttr(el, 'slot')
  if (slotTarget) {
    el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
    el-slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
    if (el.tag !== 'template' && !el.slotScope) {
      addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
    }
  }
}

processAttrs

js
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (let i = 0, l = list.length; i < l; i++) {
    // 循环之前解析到的AST属性列表[{ name, value }]
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      // 各个指令匹配
      //^v-|^@|^:|^#/
      el.hasBindings = true // 标识有绑定的值
      modifiers = parseModifiers(name.replace(dirRE, '')) // 获取modifier (v-a.b)中的b
      name = name.replace(modifierRE, '') // 去掉修饰符,拿到纯净的指令名称
    }
    // 上面指令处理过后,这里的name已经是单纯的指令没有修饰符
    if (bindRE.test(name)) {
      // v-bind
      name = name.replace(bindRE, '') // 绑定属性名 :name => name
      value = parseFileters(value) 
      // 这里会处理filter的情况,原理和解析标签一样,循环处理整个字符串,碰到相应的字符进行处理,最后得到类似_f("filterName")(exp = 传入的属性值),每次得到的新的 _f 也作为下次匹配到的filter 的属性值
      isDynamic = dynamicArgRE.test(name) // 是否是动态属性名[name] = value
      if (isDynamic) {
        name = name.slice(1, -1)
      }
      ...
      if (modifiers.sync) {
        // 有sync修饰符
        syncGen = genAssignmentCode(value, `$event`)
        // addHandler 往el(AST)元素的event 属性添加 对应的事件
        if (isDynamic) {
          addHandler(el, `update:${camelize(name)}`), syncGen, null, false, warn, list[i])
        } else {
          addHandler(le, `"update:" + (${name})`, syncGen, null, false, warn, list[i], true)
        }
        ....
      }
    } else if (onRE.test(name)) {
      // v-on 事件,也是添加到 event
      name = name.replace(onRE, '')
      isDynamic = dynamicArgRE.test(name)
      ...
      addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
    } else {
      // v-model 会走到 自定义指令也会
      name = name.replace(dirRE, '')
      const argMatch = name.match(argRE) // 匹配指令的 binding.argument
      let arg = argMatch && argMatch[1]
      isDynamic = false
      if (arg) {
        name = name.slice(0, -(arg.length + 1)) // 去除 :arg
        if (dynamicArgRE.test(arg)) {
          arg = arg.slice(1, -1)
          isDynamic = true
        }
        // el.directive.push(obj) 是一个数组
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
      }
    } else {
      ...
      // 普通属性 推入 el.attrs 数组
      addAttr(el, name, JSON.Stringify(value), list[i])
    }
  }
}

chars 实现

js
chars (text, start, end) {
  if (!currentParent) {
    return
    // 文本定义在根节点之外
  }
  ...
  const children = currentParent.children
  if (inPre || text.trim()) {
    // script / style 标签,直接返回文本
    // 其他的,要做decode
    text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
  } else if (!children.length) {
    text = ''
    // 移除开始标签后面的空格
  }
  ...
  if (text) { 
		if (!inPre && whitespaceOption === 'condense') {
      text = text.replace(whitespaceRE, ' ') // 多个空格替换成一个
    }
    let res
    let child
    if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
      // 有插值的文本 {{text}}
      child = {
        type: 2,
        expression: res.expression,
        token: res.tokens,
        text
      }
    } else if (text !== ' ' || !children.length || children[children.length -1].text !== ' ') {
      // 纯文本 <span>123</span>
      child = {
        type: 3,
        text,
      }
    }
    if (child) {
      children.push(child) // text的AST节点加到当前父节点
    }
  }
  
}

parseText

js
// compiler/parser/text-parser.js
function parseText (text, dlimiters) {
  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
  // const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
  // 插值的正则匹配
  const tokens = []
  const rowTokens = []
  let lastIndex = tagRE.lastIndex = 0
  let match, index, tokenValue
  while ((match = tegRE.exec(text))) {
    index = match.index
    if (index > lastIndex) {
			rawTokens.push(tokenValue = text.slice(lastIndex, index))
      tokens.push(JSON.stringify(tokenValue))
    }
    const exp = parseFilters(match[1].trim()) // filters
    tokens.push(`_s(${exp}`)
    rawTokens.push({'@binding': exp})
    lastIndex = index + match[0].length
  }
  if (lastIndex < text.length) {
    rawTokens.push(tokenValue = text.slice(lastInex))
    tokens.push(JSON.stringify(tokenValue))
  }
  // 将文本逐个单词都token化,最后形成token数组和表达式返回
  return {
    expression: tokens.join('+'),
    tokens: rawTokens,
  }
}