概览
Vue.js作为目前最风行的前端框架之一,一些概念和原理还是须要咱们前端开发人员理解与深刻了解的。
Vue.js波及的知识点很多,一些重要概念,例如:如何应用proxy
实现响应式effect
,虚构DOM
的Diff
算法及演变过程(双端Diff算法、疾速Diff算法等),渲染器原理的实现,编译器、解析器的工作原理,动静节点、动态晋升等等;
当初重点采纳图解步骤剖析一下编译器的简略工作原理;
编译器概念
编译器其实就是一段JavaScript
代码程序,它将一种语言(A
)编译成另外一种语言(B
),其中前者A
通常被叫做源代码,后者B
通常被叫做为指标代码。例如咱们vue的前端我的项目的.vue
文件个别即为源代码,而编译后dist
文件里的.js
文件即为指标代码;这个过程就被称为编译(compile
)
要害概念
次要波及的概念:
DSL
畛域特定语言AST
形象语法树(Abstract Syntax Tree)- 无限状态机
- 深度优先算法
简略流程
一个规范的编译器流程如下图所示:Vue.js
作为DSL
,其编译流程会与上图有所不同,对于Vue.js
来说,源代码就是组件的模板代码,而指标代码就是可能在浏览器(或其余平台)平台上运行的JavaScript
代码。
Vue的编译器
Vue.js
的指标代码其实就是渲染函数(render
函数)。详情而言,Vue.js
编译器首先对模板进行词法剖析、语法分析,而后失去模板的形象语法树(AST
)。随后将模板AST
转换成JavaScript
AST,最初再转换成JavaScript
代码,及渲染函数。一个简略的Vue.js模板编译器的工作流如下:
简略如下:
模板代码
<div> <h1 id="vue">vue_compiler</h1></div>
指标的AST
const ast = { type: 'Root', children: [ { type: 'Element', tag: 'div', children: [ { type:'Element', tag: 'h1', props: [ { type: 'Attribute', name: 'id', content: 'vue' } ], children: [ { type: 'Text', content: 'vue_compiler' } ] } ] } ]}
指标代码
function render() { return h('div', [ h('h1', {id: 'vue'}, 'vue_compiler') ])}
由以上代码能够看出,AST
其实就是一个具备层级构造的对象,模板的AST
与模板具备雷同的嵌套构造。每一颗AST
都有一个逻辑上的根节点,其类型为Root
,而模板中真正的根节点则作为Root
节点的children
存在。
察看AST
可知:
- 不同类型的节点是通过节点的
type
属性进行辨别的。 - 标签节点的子节点存储在其
children
数组中。 - 标签节点的属性节点会存储在
props
数组中。 - 不同类型的节点会应用不同的对象属性进行形容。
编译过程
parse函数
Vue.js
通过封装parse
函数,实现对模板的词法剖析和语法分析,最终失去模板的AST。parse
函数接管模板字符串作为参数,并将解析后的AST
作为返回值返回;
const template = ` <div> <h1>vue<h1> </div>`const templateAst = parse(template)
解析器是如何对模板字符串进行宰割的呢,此处就须要用到无限状态自动机。指的是在无限个状态之间,随着字符的输出,解析器会主动地在不同的状态之间进行切换。(实际上无限状态机是能够应用正则表达式来实现的)。
简略的状态机流程图:
通过无限状态机原理,能够帮忙咱们实现对模板的标记,最终将失去一系列Token
(词法标记号)。
假如有如下代码:
const template = `<div><span>Vue</span><p>Vue Compiler</p></div>` // 模板字符串// 通过无限状态机原理实现词法合成失去三个Token// 开始标签 <div>// 文本节点 vue// 完结标签 </div>// 最终值为const tokens = tokenize(template);// [// {// type: 'tag', name: 'div'// },// {// type: 'tag', name: 'span'// },// {// type: 'text', name: 'Vue'// },// {// type: 'tagEnd', name: 'span'// },// {// type: 'tag', name: 'p'// },// {// type: 'text', name: 'Vue Compiler'// },// {// type: 'tagEnd', name: 'p'// },// {// type: 'tagEnd', name: 'div'// }// ]// 此代码须要生成的AST应为const ast = { type: 'Root', children: [ { // 理论的根节点 type: 'Element', tag:: 'div', children: [ { type: 'Element', tag:: 'span', children: [ { type: 'Text', content: 'Vue' } ] }, { type: 'Element', tag:: 'p', children: [ { type: 'Text', content: 'Vue Compiler' } ] } ] } ]}
以上代码生成的AST数据结构HTML构造雷同,都是树状构造
接下来要做的就是将生成的tokens
转换成AST
,在转换过程中须要保护一个Stack
,这个栈将用来保护元素间的父子关系。每到遇到一个开始标签,就创立一个Element
类型的AST节点,并将其压入栈内,相似的,每当遇到一个完结标签节点,咱们就将以后栈顶的节点弹出。这样栈顶的节点将始终充当父节点的角色。转换过程中的所有节点,都将作为以后栈顶节点的子节点,并增加到栈顶节点的children
属性下。流程如下图示:
最后节点只有根节点Root
当扫描到第一个标签是开始节点时,因而咱们创立一个类型为Element
的AST节点Element(div)
,并将该节点作为以后节点的子节点。因为以后的栈顶节点是Root
节点,所以新创建的Element(div)
节点作为Root
节点的子节点被增加到AST
中,最初将新建的Element(div)
节点压入栈中。
因为第二个节点也是一个开始标签,所以流程同上一步,只不过以后的栈顶节点为Element(div)
,所以将以后的节点Element(span)
作为其子节点增加到AST
中,最初将Element(div)
节点压入栈中。
接下来的节点是一个文本节点,所以须要创立一个Text
类型的AST
节点,并将其作为栈顶节点Element(span)
的子节点退出到AST中,不同的时,以后接待不是Element
类型,所以不须要压入栈中;
上面是一个完结标签节点,依据规定,则须要将以后栈顶的节点弹出。
前面的流程此处就不再累述
最终实现后的成果如下:
当初咱们来实现parse函数
function parse(str) { // 对模板进行词法剖析,失去节点list const tokens = okenize(template); // 创立跟节点 const root = { type: 'Root', children: [] }; // 创立节点栈,root节点作为栈的根节点 const stack = [root]; while(tokens.length) { const parent = stack[stack.length - 1]; const token = tokens[0] // 从第一个点开始 switch(t.type) { case 'tag': const eleNode = { type: 'Element', tag: t.name, children: [] } parent.children.push(eleNode); stack.push(eleNode); break; case 'text': const textNode = { type: 'Text', content: t.content } parent.children.push(textNode); break; case 'tagEnd': // 完结标签,将栈顶节点弹出栈 stack.pop(); break; } // 生产掉已解决的节点 tokens.shift() } return root}
以上就是一个简版的parse函数的实现,当然绝对于Vue.js的源码还有很多差别,但基本原理大致相同。
上面对于transform
函数和generate
函数仅做了简要阐明,具体实现原理敬请期待;
transform函数
const template = ` <div> <h1>vue<h1> </div>`const templateAst = parse(template)const jsAst = transform(templateAst)
generate函数
const template = ` <div> <h1>vue<h1> </div>`const templateAst = parse(template)const jsAst = transform(templateAst)const code = generate(jsAst)
残缺流程
以上就是Vue
模板编译器的根本构造和工作流程,它次要有三个局部组成:
- 用来将模板字符串解析为模板
AST
的解析器(parser
); - 用来将模板
AST
解析成JavaScript AST
的转换器(transformer
); - 用来依据
JavaScript AST
生成渲染函数代码的生成器(generator
);
本文章次要探讨了parser的根本实现原理(实际上Vue.js的真正实现要简单的多,比方正则解析、Vue语法解析v-if、v-show、内插值{{}}等等),以及如何应用无限状态自动机来结构一个词法分析器,其过程就是状态机在不同的状态之间进行迁徙的过程,并生成一个Token列表汇合。而后应用Token列表汇合和顶节点元素栈来结构一个能够用来形容模板的AST,最初应用模板AST来解析成JavaScript AST和渲染函数。
作者:GFE-绝对零度
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
参考
Vue.js源码;
Vue.js设计与实现;