好家伙,书接上回

 

在上一篇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使用iframe嵌入html,js方法互调

    前段时间 使用h5搞了个用cesium.js做的地图服务功能,后来想整合到vue项目,当然最简单的就是iframe直接拿来用了. 但html和vue的方法交互就是成了问题,vue调用html种方法还好 ...

  2. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-2-playwright的API及其他知识

    1.简介 上一篇宏哥已经将Python+Playwright的环境搭建好了,而且也简单的演示了一下三款浏览器的启动和关闭,是不是很简单啊.今天主要是把一篇的中的代码进行一次详细的注释,然后说一下pla ...

  3. 驱动开发:内核实现SSDT挂钩与摘钩

    在前面的文章<驱动开发:内核解析PE结构导出表>中我们封装了两个函数KernelMapFile()函数可用来读取内核文件,GetAddressFromFunction()函数可用来在导出表 ...

  4. Oracle rac环境的数据库导入操作记录

    搞某一大项目的性能测试,将Oracle数据库dmp文件(211G)导入性能测试环境. 因为Oracle部署方式为rac,数据存储到共享磁盘.但由于对rac了解不足,这个过程中犯了两个错误: ① 表空间 ...

  5. 【技术积累】Java中的泛型【一】

    泛型是什么 Java中的泛型是一种能够让用户在编写代码时避免使用明确的类型而进行类型参数化的机制.Java中的泛型可以让编程者在代码编写时不必关心具体类型,只用关心类型之间的关系和相互转换,从而在编写 ...

  6. .NET指定图片地址下载并转换Base64字符串

    需求描述 需要调用第三方图片上传接口上传图片,对方图片格式只能接收Base64字符串.所以我们需要将系统服务器的图片通过Url下载下来,然后转换成Base64字符串.接下来我们将使用HttpClien ...

  7. 深入了解ApacheZeppelin:如何构建高效的数据科学平台

    目录 引言 随着数据科学和人工智能的快速发展,如何构建高效的数据科学平台已经成为一个重要议题.Apache Zeppelin是一个开源的数据科学平台,其提供了一种简单.高效的方式来处理和存储数据,并且 ...

  8. Java 设计模式实战系列—策略模式

    从优惠打折活动说起 电商平台为了增加销量经常搞一些活动,比如 618.双十一,还有一些节假日活动,根据销量的变化又经常更新不同的活动.最开始为了增加销量,全场都六折: // 打六折 public Bi ...

  9. 利用代码生成工具快速生成基于SqlSugar框架的Winform界面项目

    我们接触一个新事物的时候,如果一个事物能够给我们带来非常直观的感官认识,那么我们就很容易接受,反之可能需要很长时间的潜移默化的了解认识才能接受.万物化繁为简,透过本质看表象,往往也是一个认知迭代深入的 ...

  10. Ryu控制器教程

    RYU不要使用apt的方法安装,这样的安装是不完整的, 并且相关文件不易查找. 1.下载ryu源码 cd cd Desktop git clone https://gitee.com/lpm-123/ ...