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 ...
随机推荐
- Bracket Sequence
F. Bracket Sequence time limit per test 0.5 seconds memory limit per test 256 megabytes input standa ...
- props传值遇Cannot read property getAttribute of undefined异常
今有一个echarts 图标的子组件使用watch 监听接受父组件传入的data,而在父组件页面再次根据日期筛选数据,重新传入子组件进行图表重绘时老实会提示报错 vue.runtime.esm.js? ...
- npm run serve/build 背后的真实操作
vue CLI 用起来的确很舒服,方便省事,但他经过层层封装很难明白,执行完那个npm run serve/build 后他都干了些什么,甚至不知道整个项目是怎么跑起来的,今天自己抽时间就去瞅瞅,为加 ...
- ODOO13之12:Odoo 13开发之报表和服务端 QWeb
报表是业务应用非常有价值的功能,内置的 QWeb 引擎是报表的默认引擎.使用 QWeb 模板设计的报表可生成 HTML 文件并被转化成 PDF.也就是说我们可以很便捷地利用已学习的 QWeb 知识,应 ...
- R 语言关于 SSL 证书异常处理笔记
一.关于 TCGAbiolinks TCGAbiolinks 是一个用于 TCGA 数据综合分析的 R/BioConductor 软件包,能够通过 GDC Application Programmin ...
- 看看Angular有啥新玩法!手把手教你在Angular15中集成报表插件
摘要:本文由葡萄城技术团队于博客园原创并首发.葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. Angular15新特性 Angular框架(以下简称"Angular" ...
- Vue——计算属性、监听属性、Vue生命周期、组件介绍和使用、组件间通信、ref属性
计算属性 // 1 计算属性是基于他们的依赖变量进行缓存的 // 2 计算属性只有在它的相关依赖变量发生改变时才会重新求值,否则不会变(函数只要页面变化,就会重新运算) // 3 计算属性就像pyth ...
- debug.exe的使用
debug.exe的使用 debug.exe 是 Windows 操作系统自带的一个命令行调试工具,用于在 DOS 环境下进行汇编语言级别的调试操作.它可以让程序员以来自底层的方式逐步执行代码并检查每 ...
- CKS 考试题整理 (07)-RBAC - RoleBinding
Context 绑定到 Pod 的 ServiceAccount 的 Role 授予过度宽松的权限,完成以下项目以减少权限集. Task 一个名为 web-pod 的现有 Pod 已在 namespa ...
- [ARM 汇编]进阶篇—异常处理与中断—2.4.2 ARM处理器的异常向量表
异常向量表简介 在ARM架构中,异常向量表是一组固定位置的内存地址,它们包含了处理器在遇到异常时需要跳转到的处理程序的入口地址.每个异常类型都有一个对应的向量地址.当异常发生时,处理器会自动跳转到对应 ...