代码 执行 结果 参考 剖析 Vue.js 内部运行机制

代码

// 匹配到注释节点,文档类型节点,开闭合标签等
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const singleAttrIdentifier = /([^\s"'<>/=]+)/
const singleAttrAssign = /(?:=)/
const singleAttrValues = [/"([^"]*)"+/.source, /'([^']*)'+/.source, /([^\s"'=<>`]+)/.source]
const attribute = new RegExp('^\\s*' + singleAttrIdentifier.source + '(?:\\s*(' + singleAttrAssign.source + ')' + '\\s*(?:' + singleAttrValues.join('|') + '))?')
const qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')'
const startTagOpen = new RegExp('^<' + qnameCapture)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>')
const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
// 用于解析标签的层级关系与父子关系
const stack = []
let currentParent, root

// 前进索引
let index = 0
// 前进方法
function advance(n) {
  index += n
  html = html.substring(n)
}

// 把属性转换成map格式
function makeAttrsMap(attrs) {
  const map = {}
  for (let i = 0, l = attrs.length; i < l; i++) {
    map[attrs[i].name] = attrs[i].value
  }
  return map
}
// 解析起始标签
function parseStartTag() {
  const start = html.match(startTagOpen)

  if (start) {
    const match = {
      tagName: start[1],
      attrs: [],
      start: index
    }
    advance(start[0].length)

    let end, attr
    while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
      advance(attr[0].length)
      match.attrs.push({
        name: attr[1],
        value: attr[3]
      })
    }
    if (end) {
      match.unarySlash = end[1]
      advance(end[0].length)
      match.end = index
      return match
    }
  }
}
// 解析末尾标签
function parseEndTag(tagName) {
  let pos
  for (pos = stack.length - 1; pos >= 0; pos--) {
    if (stack[pos].lowerCasedTag === tagName.toLowerCase()) {
      break
    }
  }

  if (pos >= 0) {
    if (pos > 0) {
      currentParent = stack[pos - 1]
    } else {
      currentParent = null
    }
    stack.length = pos
  }
}
// 解析文本标签,一种是表达式,一种是普通文本
function parseText(text) {
  if (!defaultTagRE.test(text)) return

  const tokens = []
  let lastIndex = (defaultTagRE.lastIndex = 0)
  let match, index
  while ((match = defaultTagRE.exec(text))) {
    index = match.index

    if (index > lastIndex) {
      tokens.push(JSON.stringify(text.slice(lastIndex, index)))
    }

    const exp = match[1].trim()
    tokens.push(`_s(${exp})`)
    lastIndex = index + match[0].length
  }

  if (lastIndex < text.length) {
    tokens.push(JSON.stringify(text.slice(lastIndex)))
  }
  return tokens.join('+')
}

// 从el的attrsList 属性中取出 name 对应值
function getAndRemoveAttr(el, name) {
  let val
  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)
        break
      }
    }
  }
  return val
}
// 处理v-for指令,解析成for属性和alias属性
function processFor(el) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const inMatch = exp.match(forAliasRE)
    el.for = inMatch[2].trim()
    el.alias = inMatch[1].trim()
  }
}
// 处理v-if指令:v-if的条件存入ifConditions
function processIf(el) {
  const exp = getAndRemoveAttr(el, 'v-if')
  if (exp) {
    el.if = exp
    if (!el.ifConditions) {
      el.ifConditions = []
    }
    el.ifConditions.push({
      exp: exp,
      block: el
    })
  }
}

function parseHTML() {
  while (html) {
    last = html
    let textEnd = html.indexOf('<')
    // 如果匹配到末尾了
    if (textEnd === 0) {
      // 注释节点
      if (comment.test(html)) {
        const commentEnd = html.indexOf('-->')
        if (commentEnd >= 0) {
          if (currentParent) {
            currentParent.children.push({
              type: 3,
              text: html.substring(4, commentEnd),
              isComment: true
            })
          }
          advance(commentEnd + 3)
          continue
        }
      }
      // 条件注释
      if (conditionalComment.test(html)) {
        const conditionalEnd = html.indexOf(']>')

        if (conditionalEnd >= 0) {
          advance(conditionalEnd + 2)
          continue
        }
      }
      // Doctype
      const doctypeMatch = html.match(doctype)
      if (doctypeMatch) {
        advance(doctypeMatch[0].length)
        continue
      }
      // 获取到匹配的End标签
      const endTagMatch = html.match(endTag)
      if (endTagMatch) {
        // 前进endTagMatch[0].length个位置
        advance(endTagMatch[0].length)
        // 从stack栈中取出最近跟自己标签名一致的元素,将currentParent指向那个元素,并将该元素之前的元素都出栈
        // 因为可能有自闭合标签,所以要在stack的第二个位置找到同名标签
        parseEndTag(endTagMatch[1])
        continue
      }
      // 如果匹配到了开始标签
      if (html.match(startTagOpen)) {
        //解析开始标签
        const startTagMatch = parseStartTag()
        const element = {
          type: 1,
          tag: startTagMatch.tagName,
          lowerCasedTag: startTagMatch.tagName.toLowerCase(),
          attrsList: startTagMatch.attrs,
          attrsMap: makeAttrsMap(startTagMatch.attrs),
          parent: currentParent,
          children: []
        }
        // 处理v-if指令:v-if的条件存入ifConditions
        processIf(element)
        // 处理v-for指令,解析成for属性和alias属性
        processFor(element)
        // root指向开始标签,开始标签就是ast的root
        if (!root) {
          root = element
        }
        //如果currentParent存在,也就是这层遍历的是children
        if (currentParent) {
          currentParent.children.push(element)
        }
        // 如果不是自闭合标签,才能有currentParent
        if (!startTagMatch.unarySlash) {
          stack.push(element)
          currentParent = element
          lastTag = element.tag
        }
        continue
      }
    } else {
      // 如果匹配到text文本,将文本提取出来
      text = html.substring(0, textEnd)
      // 前进
      advance(textEnd)
      let expression
      // 如果是表达式
      if ((expression = parseText(text))) {
        // 是一个表达式
        currentParent.children.push({
          type: 2,
          text,
          expression
        })
      } else {
        // 是一个纯文本
        currentParent.children.push({
          type: 3,
          text
        })
      }
      continue
    }
  }
  return root
}

function parse() {
  return parseHTML()
}

执行

var html = '<div :class="obj.name" class="test" data-name="div" v-if="index>0" v-for="(item,index) in arr"><!-- hello --><img src="logo.png"/><p>hello world{{name}}</p></div>'
const ast = parse(html)
console.log(ast)

结果

parse结果

参考

剖析 Vue.js 内部运行机制

暂无评论