大白话Vue源码系列(02):编译器初探
Vue 的编译器模块相对独立且简单,本篇就从这块入手,先把它干掉。
编译器代码入口文件
前面已经提到,Vue 项目中的 entry-runtime.js 文件是 Vue 用于构建 仅包含运行时 的源码文件,而 entry-runtime-with-compiler.js 是用于构建 同时包含编译器和运行时 的全功能文件。因此两个文件的差集必然就是编译器实现。
先看一下 entry-runtime.js 文件的内容:
import Vue from './runtime/index'
export default Vue
文件里总共就这两行代码。这样的话就基本确定编译器相关的代码就在 entry-runtime-with-compiler.js 文件里了,事实证明也确实是这样。
Vue.prototype.$mount
entry-runtime-with-compiler.js 文件里的关键代码是为 Vue 的 prototype 扩展了一个 $mount
方法,并将模板编译相关的工作都封装在了这个 $mount 方法里。
在具体深扒 $mount
方法的内部实现之前,有必要先看一下它的应用场景是怎样的,这样会更有助于理解它内部是怎么工作的。
例如下面一段 html 模板:
<div id="index">
<div>{{msg}}</div>
</div>
开发者可以通过如下操作使用 Vue 将上面这段模板编译成 render 函数:
let vm = new Vue({
data: {
msg: 'hello',
}
});
// 实例化 Vue 时 new Vue(options) 传入的 options 可通过 vm.$options 访问
console.log(vm.$options.render);
/* Console 输出:
* undefined
*/
vm.$mount('#index');
console.log(vm.$options.render);
/* Console 输出:
* ƒ anonymous() {
* with(this){return _c('div',{attrs:{"id":"index"}},[_c('div',[_v(_s(msg))])])}
* }
*/
可以看到在调用 $mount
方法之后已经生成了 Vue 的 render 函数。
更常用也更方便的用法是:
new Vue({
el: '#index',
data: {
msg: 'hello',
},
});
这两种写法是完全等价的。实际上,如果在实例化 Vue 的时候提供了 el 选项,Vue 也是在内部调用 $mount
方法进行编译的。
接下来就看看 $mount
方法的具体是怎么实现的,为了更加清晰地描述思路,以下均使用伪代码进行书写:
/**
* 作用:将 Vue 的 html 模板编译成 render 函数。
*
* 通过将 $mount 方法定义在 Vue 的 prototype 上,
* 使得每一个 new 出来的 Vue 实例都能使用 $mount 方法。
*/
Vue.prototype.$mount = function (el){
// options 是 new Vue(options) 提供的实参 options
const options = this.$options;
// 优先使用实例化 Vue 时提供 render 函数
if (options.render) {
// 已经是 render 函数了,因此不用做任何操作
return this;
// 如果没有提供 render 函数,则优先使用提供的 template 选项
}else if(options.template){
template = getOuterHTML(options.template);
// 如果既没有提供 render 函数,又没有 template 选项,就使用 el 选项
}else{
template = getOuterHTML(el);
}
// 编译 html 模板生成 render 函数,并赋给 options 的 render 选项
// 这也是为什么上面在调用 $mount 方法之后 vm.$options.render 的值发生了变化
options.render = compileToFunctions(template);
return this;
}
// 负责兼容多样化的输入形式并返回要处理的 html模板片段
function getOuterHTML(){/*...*/}
// 负责将 html模板片段编译成 render 函数
function compileToFunctions(el){/*...*/}
可以看到,如果实例化 Vue 的时候同时提供了 render、template、el 选项中的多个,则 Vue 使用的优先级是 render > template > el。
# getOuterHTML 函数
上面的 getOuterHTML
函数所做的工作就是兼容你使用 Vue 的各种姿势,比如:
{ el: '#index' }
{ el: document.querySelector('#index') }
{ template: '#index' }
{ template: '<div>{{msg}}</div>'}
你可以传 CSS 选择器,也可以直接传 DOM, 还可以传 html 片段,怎么玩你说了算。getOuterHTML
函数的返回值是 DOM 的 outerHTML,总之,它负责得到 html 模板片段。
至此一切仍然是在扯淡,上面的都只是前戏,现在还没进入真正的编译阶段。眼贼的同学估计已经看到了,上面的 compileToFunctions 函数才是真刀实枪负责编译的。
# compileToFunctions 函数
接下来就扒进去看看 compileToFunctions
是怎么把 getOuterHTML
获得的 html 模板片段编译成 render 函数的。
compileToFunctions
函数编译模板的过程主要分为三步:
- 将 html 模板解析成抽象语法树(AST)。
- 对 AST 做优化处理。
- 根据 AST 生成 render 函数。
什么是抽象语法树
抽象语法树(Abstract Syntax Tree) 是源代码语法结构的抽象表示,并以树这种数据结构进行描述。AST 属编译原理范畴,有比较成熟的理论基础,因此被广泛运用在对各种程序语言(JavaScript, C, Java, Python等等)的编译处理中。Vue 同样也是使用 AST 作为中间形式完成对 html 模板的编译。
构建 AST 的一般过程
首先看一下第一步,也就是 解析成 AST。但是在继续 Vue 模板如何生成 AST 之前,有必要先看一下 AST 的一般解析过程。
通常程序语言解析成 AST 的过程会分为两步:
- 词法分析(Lexical Analysis)
- 语法分析(Syntax Analysis)
拿咱最熟悉的 JavaScript 来说吧,比如下面一段程序:
let a = 1
词法分析器会把代码的字符序列转换为单词序列(tokens)。经过词法分析后就能得到如下一个词素列表:
[
{ type: 'Keyword', value: 'let' },
{ type: 'Identifier', value: 'a' },
{ type: 'Punctuator', value: '=' },
{ type: 'Numeric', value: '1' }
]
语法分析器会在词法分析的基础上将单词序列(tokens)组合成各类语法短语(语句、表达式等)。经过语法分析后即可得到 AST 的 JSON 格式:
{
type: "Program",
body: [
{
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "a"
},
init: {
type: "Literal",
value: 1,
raw: "1"
}
}
],
kind: "let"
}
],
sourceType: "script"
}
上面的英文单词大家不认识的自己去搜下翻译哈。JSON 是天然的树形结构,树形图想必诸位早就脑补出来了吧:
源代码生成的抽象语法树
以上是使用 Esprima 工具对 JS 代码进行词法分析和语法分析的结果。
这里有一个 在线的AST生成工具。
还有一个 AST树形图预览工具。
Vue 构建的 AST
扯了这么多,应该对抽象语法树有个模糊的概念了吧,这对理解 Vue 的 AST 构建过程就足够用了。
回到正题,Vue 的 html 模板比较特殊,因为它根本算不上是一门语言,而是基于 HTML 的声明式绑定。因此,Vue 生成的 AST 类似于大家已经非常熟悉且非常成熟的 DOM 树,实际上 Vue 也确实是仿照着 DOM 树进行解析的。只要你熟悉 DOM 树,Vue 生成的 AST 是灰常好看且简单的。如果连 DOM 树都不了解,那咱只能帮你到这里了,你一定是个假前端。
最后再次强调的一点是,Vue 编译器的编译结果是一个函数——Vue 的 render 函数,AST 只是方便处理的中间形式。
本篇完,将在下篇深究 Vue 构建 AST 的细节。
本系列会以每周一篇的速度持续更新,喜欢的小伙伴记得点关注哦。
大白话Vue源码系列(02):编译器初探的更多相关文章
- 大白话Vue源码系列(03):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(04):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(03):生成AST
阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...
- 大白话Vue源码系列(05):运行时鸟瞰图
阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...
- 大白话Vue源码系列(01):万事开头难
阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...
- 大白话Vue源码系列目录
.first-level{ font-size: 1.2rem; cursor: default; color: #666; } .second-level{ font-size: 1.1rem; p ...
- 手牵手,从零学习Vue源码 系列一(前言-目录篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- Vue源码学习02 初始化模块init.js
接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...
随机推荐
- Cycling
Cycling Time Limit: 5000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- 多项式求和,素数判定 HDU2011.2012
HDU 2011:多项式求和 Description 多项式的描述如下: 1 - 1/2 + 1/3 - 1/4 + 1/5 - 1/6 + ... 现在请你求出该多项式的前n项的和. Input ...
- easyui dialog 中嵌入html页面
最近使用easyui比较多,这个插件确实很好用.在使用时也遇到了大大小小的问题,好在都一一解决了. 记录一下今天遇到的问题. 目的:用easyui的dialog嵌入一个html页面(html中仍有要执 ...
- Webpack 入门教程
Webpack 是一个前端资源加载/打包工具.它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源. 本章节基于 Webpack3.0 测试通过. 从图中我们可以看出,W ...
- Jxl创建Excel文件和解析Excel文件
import java.io.File; import jxl.Workbook; import jxl.write.Label; import jxl.write.WritableSheet; im ...
- 集合>哈希表类Hashtable和SortedList排序列表类
集合>哈希表类Hashtable Hashtable一种键值对的集合 ,哈希表内部的排列是无序的,而且哈希表没有提供排序方法. 集合>哈希表类Hashtable>构造普通哈希表 代码 ...
- C#多功能DataGridView打印类(WinForm)
; printPreviewDialog.ShowDialog(); } catch { ...
- Machine Learning &&Deep Learning&&Sklearn
参考资料:https://github.com/ty4z2008/Qix/blob/master/dl.md https://morvanzhou.github.io/ 如图,先了解一下都有什么模型方 ...
- [O]ORACLE物化视图的使用
用于数据复制的物化视图 物化视图的一个主要功能就是用于数据的复制,Oracle推出的高级复制功能分为两个部分,多主复制和物化视图复制.而物化视图复制就是利用了物化视图的功能. 物化视图复制包含只读物化 ...
- canvas动画3:交互
canvas动画3 时隔很久,本人终于又写博客了(重度拖延症),把之前留下的canvas交互动画讲一讲. 电脑上的交互,指的是鼠标和键盘,我们今天主要用的是鼠标. 既然是鼠标的话,就要获取鼠标的各种事 ...