Vue源码学习(三):<templete>渲染第二步,创建ast语法树
好家伙,书接上回
在上一篇Vue源码学习(二):<templete>渲染第一步,模板解析中,我们完成了模板解析
现在我们继续,将模板解析的转换为ast语法树
1.前情提要
代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍,
胜过我解释给你听一万遍
function start(tag, attrs) { //开始标签
console.log(tag, attrs, '开始的标签')
}
function charts(text) { //获取文本
console.log(text, '文本')
}
function end(tag) { //结束的标签
console.log(tag, '结束标签')
}
在这里,我们知道start,charts,end分别可以拿到
我们的`开始标签`,`文本`,`结束标签`
效果如下:(仔细看,这也是我们实验要用到的例子)



随后我们开始改造这几个方法
2.代码详解
2.1.ast树节点的结构
确定我们ast树节点的结构:
let root; //根元素
let createParent //当前元素的父亲
let stack = []
function createASTElement(tag, attrs) {
return {
tag,
attrs,
children: [],
type: 1,
parent: null
}
}
节点元素分别为
- tag:标签名
- attrs:标签属性
- children:子元素(数组)
- type:类型(后面会用到,目前"1"代表标签"3"代表文本)
- parent:父元素
2.2.start()方法
function start(tag, attrs) { //开始标签
let element = createASTElement(tag, attrs) //生成一个开始标签元素
//查看root根元素是否为空
//若是,将该元素作为根
//非原则
if (!root) {
root = element
}
createParent = element
stack.push(element)
console.log(tag, attrs, '开始的标签')
}
此处,生成一个开始标签元素,判断root是否为空,若为空,则将该元素作为根元素
随后将该元素作为父元素.
2.3.charts()方法
function charts(text) { //获取文本
console.log(text, '文本')
// text = text.replace(/a/g,'')
if(text){
createParent.children.push({
type:3,
text
})
}
// console.log(stack,'stack')
}
这个好理解,将"文本内容"作为父元素的孩子
2.4.end()方法
function end(tag) { //结束的标签
let element = stack.pop()
createParent = stack[stack.length - 1]
if (createParent) { //元素闭合
element.parent = createParent.tag
createParent.children.push(element)
}
console.log(tag, '结束标签')
}
此处,我们先将栈stack最新的元素弹出栈(作为当前元素,我们要对他进行操作),
随后获取栈的前一个元素作为父元素,
当前元素的父元素属性指向父元素的标签属性
随后将该元素推入父元素的children中,
emmmm,我还是说人话吧
假设现在stack=['div','h1']
然后pop了,createParent = 'h1'
'h1'.parent =>'div'
'div'.children =>'h1'
(多看几遍就理解了,其实非常简单)
来看看最终实现的ast语法树长什么样子

(父子关系和谐)
搞定啦!
3.完整代码
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 let root; //根元素
let createParent //当前元素的父亲
let stack = []
function createASTElement(tag, attrs) {
return {
tag,
attrs,
children: [],
type: 1,
parent: null
}
} function start(tag, attrs) { //开始标签
let element = createASTElement(tag, attrs) //生成一个开始标签元素
//查看root根元素是否为空
//若是,将该元素作为根
//非原则
if (!root) {
root = element
}
createParent = element
stack.push(element)
console.log(tag, attrs, '开始的标签')
} function charts(text) { //获取文本
console.log(text, '文本')
// text = text.replace(/a/g,'')
if(text){
createParent.children.push({
type:3,
text
})
}
// console.log(stack,'stack')
} function end(tag) { //结束的标签
let element = stack.pop()
createParent = stack[stack.length - 1]
if (createParent) { //元素闭合
element.parent = createParent.tag
createParent.children.push(element)
}
console.log(tag, '结束标签')
} export 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
}
Vue源码学习(三):<templete>渲染第二步,创建ast语法树的更多相关文章
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- vue 源码学习三 vue中如何生成虚拟DOM
vm._render 生成虚拟dom 我们知道在挂载过程中, $mount 会调用 vm._update和vm._render 方法,vm._updata是负责把VNode渲染成真正的DOM,vm._ ...
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- Vue源码后记-vFor列表渲染(1)
钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...
- Vue源码学习(一):调试环境搭建
最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...
- Vue 源码学习(1)
概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...
随机推荐
- 微信小程序setData()异常
近来开发一个小程序的项目,遇到使用setData()始终报错的情况,其问题奇特难解- 一.操作错误截图 如上图,只要将setData放置在回调函数中就会出现异常,如果不放在回调中就正常: 好郁闷,wh ...
- Python基础 - 第一个python程序
Python程序是什么? Python源程序就是一个特殊格式的文本文件,可以使用任意文本编辑器软件做python的开发,python的文件扩展名为 .py 执行python程序的三种方式 直接调用解释 ...
- Doris(七) -- 修改表、动态和临时分区、join的优化
修改表 修改表名 -- 1.将名为 table1 的表修改为 table2 ALTER TABLE table1 RENAME table2; -- 示例 ALTER TABLE aggregate_ ...
- 使用Drone+gitea配置自己的CICD流程
什么是CI CD CI CD一般包含三个概念:持续集成(Continuous Integration ,CI),持续交付(Continuous Delivery),持续部署(Continuous De ...
- C# .NET CORE .NET6 RSA 公钥加密 私钥解密
环境说明: .NET CORE 版本:.NET 6 . .NET CORE 对于RSA的支持: 1. .NET 6 中内置了对 PKCS1,PKCS8 2种私钥格式的支持. 2. 如果你要部署在Lin ...
- Vue3基本功能实现
vue3 介绍 # Vue3的变化 # 1.性能的提升 打包大小减少41% 初次渲染快55%, 更新渲染快133% 内存减少54% # 2.源码的升级 使用Proxy代替defineProperty实 ...
- WPF 入门笔记 - 05 - 依赖属性
如果预计中的不幸没有发生的话,我们就会收获意外的喜悦. --人生的智慧 - 叔本华 WPF属性系统 这一部分是中途加的,直接依赖属性有点迷糊,正好有了绑定的基础,理解起来还一些. WPF提供一组服务, ...
- js修改css样式
修改宽度: document.getElementById("div1").style.width="200px"; 修改border-radius: docu ...
- Windows服务启动exe无界面终极解决方案
1.前言 我这个方案(C#操作)是彻底解决[从Windows服务启动程序exe,程序无界面]问题的终极解决方案,终极方案,绝对的终极方案,本来打算收钱的,还是算了,你们也不容易,关注我一下就行.后 ...
- HCL实验6:静态路由
拓扑图 步骤: 连线,路由器与路由器通过S端口连接 配置好PC 配置路由器端口IP 配置路由器的下一跳地址(静态路由) 详细步骤 连线情况可见拓扑图 配置好PC 端口IP R1 [R1]int g0/ ...