好家伙,书接上回

 

在上一篇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语法树的更多相关文章

  1. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  2. vue 源码学习三 vue中如何生成虚拟DOM

    vm._render 生成虚拟dom 我们知道在挂载过程中, $mount 会调用 vm._update和vm._render 方法,vm._updata是负责把VNode渲染成真正的DOM,vm._ ...

  3. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  4. Vue源码学习1——Vue构造函数

    Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...

  5. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

  6. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

  7. VUE 源码学习01 源码入口

    VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...

  8. Vue源码后记-vFor列表渲染(1)

    钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...

  9. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  10. Vue 源码学习(1)

    概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...

随机推荐

  1. vue CLI 4 创建项目fetchMetadata资源拉取过慢解决方法

    最近将vue cli 升级到了 v4.0.5 创建项目的时候发现慢得想个蜗牛 仔细看这个日志会发现,mapToRegistry uri 是 taobao 这个一拉就是半天,还指不定那个时候就卡死在那里 ...

  2. CodeQl lab learn

    step-3 query a function named strlen import cpp from Function f where f.getName() = "strlen&quo ...

  3. 如何卸载 python setup.py install 安装的包?

    当我们半自动安装某些 python 包时,总是存在很多依赖关系的问题,而这些问题还是很难避免的,所以,当我们安装一个不确定的包的时候,最好提前收集一些相关资料,或者请教他人,同时最好把安装过程都记录下 ...

  4. Python time strftime() 方法的使用

    1.描述 strftime() 用于格式化时间,返回以可读字符串表示的时间,格式自定义. 2.说明 python中日期和时间的格式化符号有很多,下面列举常用的符号:  %y 两位数的年份表示(00-9 ...

  5. Spring Boot实现高质量的CRUD-5

    (续前文) 9.Service实现类代码示例 ​ ​ 以用户管理模块为例,展示Service实现类代码.用户管理的Service实现类为UserManServiceImpl.​UserManServi ...

  6. ChatGPT小型平替之ChatGLM-6B本地化部署、接入本地知识库体验

    本文期望通过本地化部署一个基于LLM模型的应用,能让大家对构建一个完整的应用有一个基本认知.包括基本的软硬环境依赖.底层的LLM模型.中间的基础框架及最上层的展示组件,最终能达到在本地零编码体验的目的 ...

  7. 01-面试必会-JAVA基础篇

    1. Final 有什么用? 展开查看 被 final 修饰的类不可以被继承 被 final 修饰的方法不可以被重写 被 final 修饰的变量不可以被改变, 被 final 修饰不可变的是变量的引用 ...

  8. 如何优化数据warehouse的搜索和查询

    目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.2.1 查询优化 2.2.2 索引优化 2.2.3 数据访问优化 2.3 相关技术比较 2.3.1 SQL 2. ...

  9. 多个commit合并为一个

    在进行多个commit合并成一个博客编写的过程中,你可以使用以下代码示例作为参考: # 合并多个commit git rebase -i HEAD~N # N代表需要合并的commit数目,例如合并最 ...

  10. C++ 数独游戏

    C++ 数独游戏 直接上代码: 1 // 数独 sudoku 2 3 #include <iostream> 4 5 using namespace std; 6 7 int P[9][9 ...