获取html字符串首先在入口文件处,使用template属性或者el属性获取到需要解析的html字符串template1.html字符串,如Vue.component(‘alert-box’, { template: &lt;div class="demo-alert-box"&gt; &lt;strong&gt;Error!&lt;/strong&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/div&gt; })2.如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板el类型:string | Element通过el获取需要解析的模版export function query (el: string | Element): Element { if (typeof el === ‘string’) { const selected = document.querySelector(el) if (!selected) { process.env.NODE_ENV !== ‘production’ && warn( ‘Cannot find element: ’ + el ) return document.createElement(‘div’) } return selected } else { return el }}解析html字符串通过while循环结合正则表达式逐步匹配拆解html字符串匹配注释与html声明文件 // Comment: // 添加在root元素下面的comment会被忽略 if (comment.test(html)) { const commentEnd = html.indexOf(’–>’) if (commentEnd >= 0) { if (options.shouldKeepComment) { options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3) } advance(commentEnd + 3) continue } } // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment // ie comment if (conditionalComment.test(html)) { const conditionalEnd = html.indexOf(’]>’) if (conditionalEnd >= 0) { advance(conditionalEnd + 2) continue } } // Doctype: // match const doctypeMatch = html.match(doctype) if (doctypeMatch) { advance(doctypeMatch[0].length) continue }匹配标签起始位置匹配标签起始位置,startTagOpen匹配到,但是startTagClose匹配失败,那么失败前的html片段就会被抛弃。const ncname = [a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeLetters}]*const qnameCapture = ((?:${ncname}\\:)?${ncname})const startTagOpen = new RegExp(^&lt;${qnameCapture}) /** 解析起始标签,使用正则匹配attrs,并将匹配到的正则数组放到attrs数组里面 / function parseStartTag () { // 标签名 const start = html.match(startTagOpen) if (start) { const match = { tagName: start[1], attrs: [], start: index } advance(start[0].length) let end, attr // 解析attr while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) { attr.start = index advance(attr[0].length) attr.end = index match.attrs.push(attr) } if (end) { // 是否匹配到自闭合符号/,匹配到则设置标志属性unarySlash=’/’ match.unarySlash = end[1] advance(end[0].length) match.end = index return match } } }匹配函数首先匹配标签起始位置,匹配失败则返回进入下一步。匹配成功则创建match对象,tagName为匹配到的标签名。然后切割html字符串,进行下一步匹配。通过while循环,匹配起始标签上的attr,动态attr,将匹配到的正则放入attrs数组。最后匹配起始标签结束符const startTagClose = /^\s(/?)>/匹配成功,则根据是否匹配到自闭合标志/来给unarySlash属性赋值,最后返回match对象,进行下一步处理。进一步处理macth对象 /** 解析上一步获取的正则attrs,保存为{name, value}格式, * 并且将被浏览器转译的换行或特殊字符或者href里面的换行反转为相应符号, * 最后将tagname,attrs等传递给调用函数的start函数 / function handleStartTag (match) { const tagName = match.tagName const unarySlash = match.unarySlash if (expectHTML) { // 如标题标签,不应该被p标签包裹,如果父级标签是p,则提前闭合这个p标签 if (lastTag === ‘p’ && isNonPhrasingTag(tagName)) { parseEndTag(lastTag) } // 如果是可以自闭合的标签,上个标签和现在的标签一样,则闭合上一个标签 if (canBeLeftOpenTag(tagName) && lastTag === tagName) { parseEndTag(tagName) } } const unary = isUnaryTag(tagName) || !!unarySlash const l = match.attrs.length const attrs = new Array(l) for (let i = 0; i < l; i++) { const args = match.attrs[i] // 优先获取匹配到的第三个正则捕获 const value = args[3] || args[4] || args[5] || ’’ const shouldDecodeNewlines = tagName === ‘a’ && args[1] === ‘href’ ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines attrs[i] = { name: args[1], value: decodeAttr(value, shouldDecodeNewlines) } if (process.env.NODE_ENV !== ‘production’ && options.outputSourceRange) { attrs[i].start = args.start + args[0].match(/^\s/).length attrs[i].end = args.end } } // 非自闭合标签,存入stack数组 if (!unary) { stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end }) // 1.修改lastTag,保存堆中的最上层数组项 lastTag = tagName } // 将【匹配到的元素返回给上一级解析 if (options.start) { options.start(tagName, attrs, unary, match.start, match.end) } }start函数解析标签起始对象createASTElementexport function createASTElement ( tag: string, attrs: Array<ASTAttr>, parent: ASTElement | void): ASTElement { return { type: 1, tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), rawAttrsMap: {}, parent, children: [] }}start (tag, attrs, unary, start) { // 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对象 type=1 let element: ASTElement = createASTElement(tag, attrs, currentParent) if (ns) { element.ns = ns } if (process.env.NODE_ENV !== ‘production’) { if (options.outputSourceRange) { element.start = start element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => { cumulated[attr.name] = attr return cumulated }, {}) } attrs.forEach(attr => { if (invalidAttributeRE.test(attr.name)) { warn( Invalid dynamic argument expression: attribute names cannot contain + spaces, quotes, &lt;, &gt;, / or =., { start: attr.start + attr.name.indexOf([), end: attr.start + attr.name.length } ) } }) } 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 ’ + &lt;${tag}&gt; + ‘, as they will not be parsed.’, { start: element.start } ) } // apply pre-transforms // 提前解析 <input :type=‘type’ v-model=‘input’ /> for (let i = 0; i < preTransforms.length; i++) { element = preTransforms[i](element, options) || element } // v-pre check if (!inVPre) { processPre(element) if (element.pre) { inVPre = true } } // pre tag if (platformIsPreTag(element.tag)) { inPre = true } // 如果是带有pre属性,跳过解析 if (inVPre) { // el.attrslist => el.attrs processRawAttrs(element) } else if (!element.processed) { // structural directives // 解析v-for= “item in items”,生成element.for,element.alias,element.ite processFor(element) // 解析v-if,v-else-if,v-else;v-if processIf(element) // element.once v-once用于渲染一次组件 processOnce(element) } // 第一个start tag 为root if (!root) { root = element if (process.env.NODE_ENV !== ‘production’) { // 不能使用slot,template,v-for在root上 checkRootConstraints(root) } } // 非自闭合 if (!unary) { // last <=> currentParent = element [] currentParent = element stack.push(element) } else { closeElement(element) }}processIf/* 如果解析到v-if,给element增加if对象,如果解析到else或者v-else-if, * 标记,等到标签闭合的时候做处理。 */function processIf (el) { const exp = getAndRemoveAttr(el, ‘v-if’) if (exp) { el.if = exp addIfCondition(el, { exp: exp, block: el }) } else { if (getAndRemoveAttr(el, ‘v-else’) != null) { el.else = true } const elseif = getAndRemoveAttr(el, ‘v-else-if’) if (elseif) { el.elseif = elseif } }}