你想要的 vue 源码分析 · 26
代码 执行 结果 参考 剖析 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)
结果

参考
即可发布评论!
暂无评论
确认评论邮箱
邀请制评论