加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

大白话Vue源码系列(02):编译器初探

发布时间:2020-12-16 23:13:30 所属栏目:百科 来源:网络整理
导读:div id="catalog"div class="bq" div style="margin-bottom: .6rem" 阅读目录 ul class="list" li style="margin-bottom: .2rem !important"a href="javascript:" scroll-to="#f_0"编译器代码入口文件 li style="margin-bottom: .2rem !important"a href="ja

<div id="catalog"><div class="bq">
<div style="margin-bottom: .6rem">阅读目录
<ul class="list">
<li style="margin-bottom: .2rem !important"><a href="javascript:" scroll-to="#f_0">编译器代码入口文件
<li style="margin-bottom: .2rem !important"><a href="javascript:" scroll-to="#f_1">Vue.prototype.$mount
<li style="margin-bottom: .2rem !important"><a href="javascript:" scroll-to="#f_2">构建 AST 的一般过程
<li style="margin-bottom: .2rem !important"><a href="javascript:" scroll-to="#f_3">Vue 构建的 AST

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 文件里了,事实证明也确实是这样。

entry-runtime-with-compiler.js 文件里的关键代码是为 Vue 的 prototype 扩展了一个 $mount 方法,并将模板编译相关的工作都封装在了这个 $mount 方法里。

在具体深扒 $mount 方法的内部实现之前,有必要先看一下它的应用场景是怎样的,这样会更有助于理解它内部是怎么工作的。

例如下面一段 html 模板:

{{msg}}

开发者可以通过如下操作使用 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 的时候同时提供了 选项中的多个,则 Vue 使用的优先级是 template > el

    # 函数

    上面的 getOuterHTML 函数所做的工作就是兼容你使用 Vue 的各种姿势,比如:

      { el: '#index' }
    • { el: document.querySelector('#index') }
    • { template: '#index' }
    • { template: '
      {{msg}}
      '}

    你可以传 CSS 选择器,也可以直接传 DOM, 还可以传 html 片段,怎么玩你说了算。getOuterHTML 函数的返回值是 DOM 的 outerHTML,总之,

    至此一切仍然是在扯淡,上面的都只是前戏,现在还没进入真正的编译阶段。眼贼的同学估计已经看到了,上面的 compileToFunctions 函数才是真刀实枪负责编译的。

    # 函数

    接下来就扒进去看看 compileToFunctions 是怎么把 getOuterHTML 获得的 html 模板片段编译成 render 函数的。

    compileToFunctions 函数编译模板的过程主要分为三步:

      将 html 模板解析成抽象语法树(AST)。
    1. 对 AST 做优化处理。
    2. 根据 AST 生成 render 函数。

    什么是抽象语法树 抽象语法树(Abstract Syntax Tree) 是源代码语法结构的抽象表示,并以树这种数据结构进行描述。AST 属编译原理范畴,有比较成熟的理论基础,因此被广泛运用在对各种程序语言(JavaScript,C,Java,Python等等)的编译处理中。Vue 同样也是使用 AST 作为中间形式完成对 html 模板的编译。

    首先看一下第一步,也就是 。但是在继续 Vue 模板如何生成 AST 之前,有必要先看一下 AST 的一般解析过程。

    通常程序语言解析成 AST 的过程会分为两步:

      词法分析(Lexical Analysis)
    1. 语法分析(Syntax Analysis)

    拿咱最熟悉的 JavaScript 来说吧,比如下面一段程序:

    let a = 1
     

    。经过词法分析后就能得到如下一个词素列表:

    [
        { type: 'Keyword',value: 'let' },{ type: 'Identifier',value: 'a' },{ type: 'Punctuator',value: '=' },{ type: 'Numeric',value: '1' }
    ]
     

    。经过语法分析后即可得到 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 的 html 模板比较特殊,因为它根本算不上是一门语言,而是基于 HTML 的声明式绑定。因此,Vue 生成的 AST 类似于大家已经非常熟悉且非常成熟的 DOM 树,实际上 Vue 也确实是仿照着 DOM 树进行解析的。只要你熟悉 DOM 树,Vue 生成的 AST 是灰常好看且简单的。如果连 DOM 树都不了解,那咱只能帮你到这里了,你一定是个假前端。

    最后再次强调的一点是,Vue 编译器的编译结果是一个函数——Vue 的 render 函数,AST 只是方便处理的中间形式

    本篇完,将在下篇深究 Vue 构建 AST 的细节。

    (编辑:李大同)

    【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

      推荐文章
        热点阅读