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 ...
随机推荐
- Python基础 - 逻辑运算符
Python语言支持逻辑运算符,以下假设变量 a 为 10, b为 20: 运算符 逻辑表达式 描述 实例 and x and y 布尔"与" - 如果 x 为 False,x a ...
- ShowMeBug 持续升级,提供高信效度支撑的技术招聘方案
去年年底,全新升级版的 ShowMeBug --一款支持实战编程的技术能力评估平台,首次揭开了它神秘的面纱. 而近日,ShowMeBug 再次迎来一系列产品更新,它将以全新的面貌,提供高信效度支撑的技 ...
- bugku_EasyMath
bugku_EasyMath 题目描述 简单的数学题 from Crypto.Util.number import getPrime, bytes_to_long from secret import ...
- RT-Thread线程构建
RT-Thread 操作系统的启动过程如下 main()函数作为其中的一个线程在运行. 如果想新建一个线程,和main()线程并行运行,步骤如下: 第一步:线程初始化函数申明 static voi ...
- JS异步解决方案及优缺点
1. 回调函数 优点: 解决了同步的问题(只要有一个任务耗时长后面的任务都会等待,会拖延程序执行) 缺点: 回调地狱 不能用try catch捕获 不能用 return setTimeout(( ...
- 2023-06-11:redis中,如何在100个亿URL中快速判断某URL是否存在?
2023-06-11:redis中,如何在100个亿URL中快速判断某URL是否存在? 答案2023-06-11: 传统数据结构的不足 当然有人会想,我直接将网页URL存入数据库进行查找不就好了,或者 ...
- React后台管理系统09 菜单组件的抽取
修改Home.tsx的内容:将主菜单的内容进行抽离,然后单独引入=>MainMenu import { Breadcrumb, Layout, Menu } from 'antd'; impor ...
- 彻底解决各种浏览器访问不了GitHub问题(注意代理)
如果有穿墙插件如Google助手 VPN SS 之类别 有可能被全局代理 首先关闭这些软件 或者浏览器插件 假设,您的本地代理端口为:1080 ,打开git base窗口进行按下列的方式设置.(在 ...
- Vue-CoreVideoPlayer 视频播放器组件
安装 cnpm install -S vue-core-video-player 快速使用 # 在main.js中 import VueCoreVideoPlayer from 'vue-core-v ...
- Pyinstaller打包 Pytest+Allure成exe文件执行时,报错ERROR: usage: apitest.exe [options] [file_or_dir] [file_or_dir] [...] xxx.exe: error: unrecognized arguments: --alluredir=.\\report\\xml --clean-alluredir
网上找了很多案例啊 都没解决问题,由本人的多次试验 终于成功解决 1.打包运行 pyinstaller -D xxx.py 打包成功后 执行exe报错 如下 2.此情况是说明 命令无法正确识 ...