5. Vue的模板编译器原理 您所在的位置:网站首页 vue字符串模板是什么 5. Vue的模板编译器原理

5. Vue的模板编译器原理

2022-10-16 09:35| 来源: 网络整理| 查看: 265

Vue的渲染机制指的是Vue怎么将单文件组件中的template转换为AST(语法树),再将AST转换成render函数,最后生成虚拟dom节点(包含建立元素节点的一切信息的JavaScript对象),并建立元素节点挂载到页面上,基本过程以下图: 本节先介绍模板编译生成render函数的过程。html

模板编译过程

模板编译成渲染函数经历了三个阶段: 将模板解析成AST、遍历AST标记静态节点以及静态根节点和使用AST生成render函数。 如下面模板为例:node

{{ message }} 复制代码

首先获取组件的模板内容express

var template = options.template; if (template) { // 针对字符串模板和选择符匹配模板 if (typeof template === 'string') { // 选择符匹配模板,以'#'为前缀的选择符 if (template.charAt(0) === '#') { // 获取匹配元素的innerHTML template = idToTemplate(template); } } else if (template.nodeType) { // 针对DOM元素匹配,获取匹配元素的innerHTML template = template.innerHTML; } else { { warn('invalid template option:' + template, this); } return this } } else if (el) { // 若是没有传入template模板,则默认以el元素所属的根节点做为基础模板 template = getOuterHTML(el); } 复制代码

获取模板后处理的核心过程以下:数组

compileToFunctions(template, { outputSourceRange: "development" !== 'production', shouldDecodeNewlines: shouldDecodeNewlines, shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) ... var compiled = compile(template, options); ... var compiled = baseCompile(template.trim(), finalOptions); 复制代码

上面的代码是在建立编译器,真正的编译过程: 解析、优化以及生成render函数,代码以下:bash

var ast = parse(template.trim(), options); if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options); 复制代码 template解析

真正的解析函数是parseHTML,它的参数是template,和一个options对象,这个对象包含了start、end、chars以及comment对标签处理的函数:app

parseHTML(template, { warn: warn$2, expectHTML: options.expectHTML, isUnaryTag: options.isUnaryTag, canBeLeftOpenTag: options.canBeLeftOpenTag, shouldDecodeNewlines: options.shouldDecodeNewlines, shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, shouldKeepComment: options.comments, outputSourceRange: options.outputSourceRange, // 处理起始标签 start: function start (tag, attrs, unary, start$1, end) { ... var element = createASTElement(tag, attrs, currentParent); ... }, // 用来处理结束标签 end: function end (tag, start, end$1) { var element = stack[stack.length - 1]; // pop stack stack.length -= 1; currentParent = stack[stack.length - 1]; if (options.outputSourceRange) { element.end = end$1; } closeElement(element); }, // 用来处理文本 chars: function chars (text, start, end) { ... }, // 处理评论内容 comment: function comment (text, start, end) { // adding anyting as a sibling to the root node is forbidden // comments should still be allowed, but ignored if (currentParent) { var child = { type: 3, text: text, isComment: true }; if (options.outputSourceRange) { child.start = start; child.end = end; } currentParent.children.push(child); } } }); return root } 复制代码

parseHTML函数核心内容为:dom

while (html) { last = html; // Make sure we're not in a plaintext content element like script/style // 父元素为正常元素 if (!lastTag || !isPlainTextElement(lastTag)) { var textEnd = html.indexOf(''); if (commentEnd >= 0) { if (options.shouldKeepComment) { options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3); } advance(commentEnd + 3); continue } } // Doctype: var doctypeMatch = html.match(doctype); if (doctypeMatch) { advance(doctypeMatch[0].length); continue } // End tag: 处理结束标签 ... // Start tag: // 解析起始标签 ... } ... } else { // 父元素为script、style、textarea的处理逻辑 ... } if (html === last) { options.chars && options.chars(html); if (!stack.length && options.warn) { options.warn(("Mal-formatted tag at end of template: \"" + html + "\""), { start: index + html.length }); } break } } 复制代码

基本过程以下:async

html={{ message }}首先获取textEnd === 0,接下来可判断html是以div标签起始的,进行parseStartTag处理 var startTagMatch = parseStartTag(); if (startTagMatch) { // 对获取起始标签的属性,生成键值对 handleStartTag(startTagMatch); if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) { advance(1); } continue } 复制代码

返回对象为函数

{ attrs: [" id="app"", "id", "=", "app", undefined, undefined, index: 0, input: " id="app">{{ message }}更新", groups: undefined, start: 4, end: 13], end: 14, start: 0, tagName: "div", unarySlash: "" } 复制代码

其中unarySlash表示是不是闭合标签。在通过handleStartTag函数处理后调用start函数优化

if (!unary) { stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs,start: match.start, end: match.end }); lastTag = tagName; } // 上面根据parseStartTag返回的对象生成attrs if (options.start) { options.start(tagName, attrs, unary, match.start, match.end); } 复制代码

options.start函数处理后生成

{attrsList: [{ end: 13 name: "id" start: 5 value: "app" }], attrsMap: {id: "app"}, children: [], end: 14, parent: undefined, rawAttrsMap: {id: {name: "id", value: "app", start: 5, end: 13}}, start: 0, tag: "div", type: 1} 复制代码

先判断标签是不是闭合标签,若是是的话直接closeElement,不是的话,更新currentParent而且将当前元素推入stack栈。

if (!unary) { currentParent = element; stack.push(element); } else { closeElement(element); } 复制代码

须要注意的是: stack栈的做用是维护DOM的层级,防止HTML标签的不匹配。 2. 这个while的第一次循环结束,html被截取为{{ message }},此时计算textEnd为13,此时处理的是文本元素

// 为文本节点 var text = (void 0), rest = (void 0), next = (void 0); if (textEnd >= 0) { rest = html.slice(textEnd); while ( !endTag.test(rest) && !startTagOpen.test(rest) && !comment.test(rest) && !conditionalComment.test(rest) ) { // < in plain text, be forgiving and treat it as text next = rest.indexOf('


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有