Vue源码学习(二):<templete>渲染第一步,模板解析
好家伙,
1.<template>去哪了
在正式内容之前,我们来思考一个问题,
当我们使用vue开发页面时,<tamplete>中的内容是如何变成我们网页中的内容的?
它会经历四步:
解析模板:Vue会解析
<template>中的内容,识别出其中的指令、插值表达式({{}}),以及其他元素和属性。生成AST:解析模板后,Vue会生成一个对应的AST(Abstract Syntax Tree,抽象语法树),用于表示模板的结构、指令、属性等信息。
生成渲染函数:根据生成的AST,Vue会生成渲染函数。渲染函数是一个函数,接收一些数据作为参数,并返回一个虚拟DOM(Virtual DOM)。
渲染到真实DOM:Vue执行渲染函数,将虚拟DOM转换为真实的DOM,并将其插入到页面中的指定位置。在这个过程中,Vue会根据数据的变化重新执行渲染函数,更新页面上的内容。
所以,步骤如下:模板解析 =》AST =》生成渲染函数 =》渲染到真实DOM
2.ast语法树是什么?
抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,之所以说是抽象的,抽象表示把js代码进行了结构化的转化,转化为一种数据结构。
这种数据结构其实就是一个大的json对象,json我们都熟悉,他就像一颗枝繁叶茂的树。有树根,有树干,有树枝,有树叶,无论多小多大,都是一棵完整的树。
简单理解,就是把我们写的代码按照一定的规则转换成一种树形结构。
举个简单的例子:
假设代码如下:
<div id="app">Hello</div>
随后我们将其转换为ast语法树(简单版本):
{
tag:'div' //节点类型
attrs:[{id:"app"}] //属性
children:[{tag:null,text:Hello},{xxx}] //子节点
}
当然,实际情况复杂得多,但总体结构不变
{
"type": "Program",
"start": 0,
"end": 32,
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXIdentifier",
"name": "div"
},
"attributes": [
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "id"
},
"value": {
"type": "Literal",
"value": "app"
}
}
],
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXIdentifier",
"name": "div"
}
},
"children": [
{
"type": "JSXText",
"value": "Hello"
},
{
"type": "JSXExpressionContainer",
"expression": {
"type": "Identifier",
"name": "msg"
}
}
],
"selfClosing": false
}
}
],
"sourceType": "module"
}
2.模板解析
来看这个例子
<div id="app">Hello{{msg}}</div>
这无非就是一个简单的<div>标签,它由三个部分组成
开始标签:
<div id="app">
文本:
Hello{{msg}}
结束标签:
</div>
似乎只要用正则表达式来匹配就可以了,(事实上也确实是这么实现的)
//从源码处偷过来的正则表达式
const attribute =
/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
//属性 例如: {id=app}
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //标签名称
const qnameCapture = `((?:${ncname}\\:)?${ncname})` //<span:xx>
const startTagOpen = new RegExp(`^<${qnameCapture}`) //标签开头
const startTagClose = /^\s*(\/?)>/ //匹配结束标签 的 >
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) //结束标签 例如</div>
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
2.1.试验实例
我们来举一个实例看看:
代码已开源https://github.com/Fattiger4399/analytic-vue.git
(关键的部分已使用绿色荧光标出,没有耐心看完整代码的话,只看有绿色荧光标记的部分就好)
项目目录如下:

首先来到index.html我们人为的制造一些假数据
注意:此处的vue是我们自己写的实验品,并非大尤的Vue
index.html
<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head> <body>
<div id="app">Hello{{msg}}</div>
<script src="dist/vue.js"></script>
<script>
//umd Vue
// console.log(Vue)
//响应式 Vue
let vm = new Vue({
el: '#app', //编译模板
}) </script>
</body> </html>
入口文件index.js
import {initMixin} from "./init"
function Vue(options) {
// console.log(options)
//初始化
this._init(options)
}
initMixin(Vue)
export default Vue
初始化脚本init.js
import { compileToFunction } from "./compile/index.js";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
// console.log(options)
let vm = this
//options为
vm.$options = options
//初始化状态
initState(vm)
// 渲染模板 el
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
//创建 $mount
Vue.prototype.$mount = function (el) {
// console.log(el)
//el template render
let vm = this
el = document.querySelector(el) //获取元素
let options = vm.$options
if (!options.render) { //没有
let template = options.template
if (!template && el) {
//获取html
el = el.outerHTML
console.log(el,'this is init.js attrs:el')
//<div id="app">Hello</div>
//变成ast语法树
let ast = compileToFunction(el)
console.log(ast,'this is ast')
//render()
}
}
}
}
来到我们的核心部分/compile/index.js中的parseHTML()方法和parseStartTag()方法
function start(tag, attrs) { //开始标签
console.log(tag, attrs, '开始的标签')
}
function charts(text) { //获取文本
console.log(text, '文本')
}
function end(tag) { //结束的标签
console.log(tag, '结束标签')
}
function parseHTML(html) {
while (html) { //html 为空时,结束
//判断标签 <>
let textEnd = html.indexOf('<') //0
console.log(html,textEnd,'this is textEnd')
if (textEnd === 0) { //标签
// (1) 开始标签
const startTagMatch = parseStartTag() //开始标签的内容{}
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
// console.log(endTagMatch, '结束标签')
//结束标签
let endTagMatch = html.match(endTag)
if (endTagMatch) {
advance(endTagMatch[0].length)
end(endTagMatch[1])
continue;
}
}
let text
//文本
if (textEnd > 0) {
// console.log(textEnd)
//获取文本内容
text = html.substring(0, textEnd)
// console.log(text)
}
if (text) {
advance(text.length)
charts(text)
// console.log(html)
}
}
function parseStartTag() {
//
const start = html.match(startTagOpen) // 1结果 2false
console.log(start,'this is start')
// match() 方法检索字符串与正则表达式进行匹配的结果
// console.log(start)
//创建ast 语法树
if (start) {
let match = {
tagName: start[1],
attrs: []
}
console.log(match,'match match')
//删除 开始标签
advance(start[0].length)
//属性
//注意 多个 遍历
//注意>
let attr //属性
let end //结束标签
//attr=html.match(attribute)用于匹配
//非结束位'>',且有属性存在
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
// console.log(attr,'attr attr'); //{}
// console.log(end,'end end')
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5]
})
advance(attr[0].length)
//匹配完后,就进行删除操作
}
//end里面有东西了(只能是有">"),那么将其删除
if (end) {
// console.log(end)
advance(end[0].length)
return match
}
}
}
function advance(n) {
// console.log(html)
// console.log(n)
html = html.substring(n)
// substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集,
// 或从开始索引直到字符串的末尾的一个子集。
// console.log(html)
}
console.log(root)
return root
}
export function compileToFunction(el) {
// console.log(el)
let ast = parseHTML(el)
console.log(ast,'ast ast')
}
注释已经非常详细了,实在看不懂的话,上机调试一遍吧
代码已开源https://github.com/Fattiger4399/analytic-vue.git
tips:
(1)parseHTML中拿到的参数html为 " el = el.outerHTML " 获取的元素
即' <div id="app">Hello{{msg}}</div> '
(2)attr = html.match(attribute)匹配后得到的数据长这样:

来看看看输出结果

成功地将我们需要的三样东西分出来了
Vue源码学习(二):<templete>渲染第一步,模板解析的更多相关文章
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- vue 源码学习二 实例初始化和挂载过程
vue 入口 从vue的构建过程可以知道,web环境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compil ...
- Vue源码学习(零):内部原理解析
本篇文章是在阅读<剖析 Vue.js 内部运行机制>小册子后总结所得,想要了解详细内容,请参考原文:https://juejin.im/book/5a36661851882538e2259 ...
- Vue源码学习(二)$mount() 后的做的事(1)
Vue实例初始化完成后,启动加载($mount)模块数据. (一)Vue$3.protype.$mount 标红的函数 compileToFunctions 过于复杂,主要是生 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Dubbo源码学习(二)
@Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...
随机推荐
- MYSQL数据库的创建和删除
打开Windows命令行,输入登录用户和密码 mysql -h localhost -u root -p 创建新数据 CREATE DATABASE zoo; 查看系统中的数据库 SHOW DATAB ...
- ODOO13之六:Odoo 13开发之模型 – 结构化应用数据
在本系列文章第三篇Odoo 13 开发之创建第一个 Odoo 应用中,我们概览了创建 Odoo 应用所需的所有组件.本文及接下来的一篇我们将深入到组成应用的每一层:模型层.视图层和业务逻辑层. 本文中 ...
- Doris(七) -- 修改表、动态和临时分区、join的优化
修改表 修改表名 -- 1.将名为 table1 的表修改为 table2 ALTER TABLE table1 RENAME table2; -- 示例 ALTER TABLE aggregate_ ...
- 「学习笔记」模运算与 BSGS 算法
取模 取模符号:\(x \bmod y\),表示 \(x\) 除以 \(y\) 得到的余数. 例如, \[5 \bmod 3 = 2\\ 7 \bmod 4 = 3\\ 3 \bmod 3 = 0\\ ...
- -Xmx参数建议设置为系统内存的多少?
在设置 -Xmx 参数时,建议将其设置为系统内存的一定比例.具体的比例需要根据应用程序的特点.系统资源的限制等各种因素进行综合考虑. 如果将 -Xmx 参数设置得过小,可能会导致 JVM 分配的堆内存 ...
- 前端学习 C 语言 —— GDB调试器
GDB调试器 我们在讲指针时用 GDB 调试段错误. 本篇将详细介绍 gdb 的最常用命令.日志记录.检测点,最后介绍如何用 gdb 调试进程以及用gdb 调试一个开源项目的调试版本 -- glmar ...
- 统信UOS系统开发笔记(八):在统信UOS上编译搭建mqtt基础环境(版本使用QMQTT::Clinet)
前言 统信uos使用到mqtt开发,需要重新编译mqtt,本篇描述统信uos20上的mqtt源码编译和环境搭建. 注意 这里下载的mqtt版本与其他几篇文章的不同,这里是使用QMQTT:: ...
- 「Python实用秘技15」pandas中基于范围条件进行表连接
本文完整示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/PythonPracticalSkills 这是我的系列文章「Python实用秘技」的第15 ...
- 【转载】AF_XDP技术详解
原文信息 作者:rexrock 出处:https://rexrock.github.io/post/af_xdp1/ 目录 1. 用户态程序 1.1 创建AF_XDP的socket 1.2 为UMEM ...
- Blazor提取出Razor类库,没有css的class的智能提示
最开始从stackoverflow上找到了答案,有两种办法,但都不太理想 后来自己找了新的办法,其实很简单,把要用的css复制到Razor类库的wwwroot文件夹中,默认是不会复制到引用Razor类 ...