好家伙,书接上回

 

在上一篇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. .NET周报 【5月第3期 2023-05-21】

    国内文章 C# 实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS) https://www.cnblogs.com/shawshank/p/17390248.html 信创是现阶段 ...

  2. docker部署gitlab CI/CD (二)终篇:部署gitlab runner和添加gitlab-ci.yml文件 终极踩坑版

    关于gitlab部署的教程还好,有的看,但到了cicd环节,简直痛苦面具,教程虽多,但断断续续,先不说大部分都是只截取片段,让人云里雾里,不会的看不懂,懂的不需要看,根据步骤跑不起来不说,改了一堆,完 ...

  3. 华为IP综合实验

    目录 实验拓扑 实验需求 实验步骤 1.配置IP地址和环回口地址 2.在总公司上进行相应VLAN划分与配置 3. SW3和SW4的互连接口启用eth-trunk,最大带宽为2G 4. SW1.SW2. ...

  4. K8S 证书详解(认证)

    K8S 证书介绍 在 Kube-apiserver 中提供了很多认证方式,其中最常用的就是 TLS 认证,当然也有 BootstrapToken,BasicAuth 认证等,只要有一个认证通过,那么 ...

  5. JavaWeb技术栈图(web服务器+web容器是何物)

    JavaWeb技术栈图(web服务器+web容器是何物) 两个重要概念web服务器+web容器 什么是Web服务器? Tomcat 服务器就是一个免费的开放源代码的 Web 应用服务器 web服务实际 ...

  6. idea设置字体大小(换主题后的字体大小设置)

    如果你是默认主题 直接这样设置字体大小 如果你换了自定义主题 如果你换了自定义主题,那么上面的设置方法会没有作用,我们需要像下面这样设置:

  7. LLaMA模型指令微调 字节跳动多模态视频大模型 Valley 论文详解

    Valley: Video Assistant with Large Language model Enhanced abilitY 大家好,我是卷了又没卷,薛定谔的卷的AI算法工程师「陈城南」~ 担 ...

  8. JVM中的编译器

    JVM中集成了两种编译器,Client Compiler和Server Compiler,它们的作用也不同.Client Compiler注重启动速度和局部的优化,Server Compiler则更加 ...

  9. Git插件报错,Appears to be a git repo or submodule

    Hexo博客需要引入第三方插件,不少包作者误把包项目得.git文件上传到github,或者在插件的github路径下直接下载插件文件夹,结果是插件内含有.git文件,导致下载别的npm包时报错npm ...

  10. 行行AI人才直播第11期:墨尔本大学数据科学高级讲师-宫明明《机器学习:从统计到因果,人工智能的发展之路》

    行行AI人才是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台. 马克斯·普朗克智能系统中心主任曾在国际数学家大会进行了题为 From Statistical to Causal Learni ...