ES6模块解析,对比CommonJS

Author Avatar
高翔 4月 07, 2017

终于有了官方的模块机制,喜大普奔

ES6模块的特点

  1. 总是处于严格模式
  2. 具有顶层 (top-level) 作用域,但又不是全局 (global) 作用域(import命令具有提升效果,不管写在哪一行都会提到模块头部首先执行)
  3. 可以通过 import 关键字从其他模块导入程序绑定 (bindings, 通常就是变量或者函数)
  4. 可以通过 export 关键字导出指定的 bingdings

严格模式(以下会报错)

  • with 语句
  • 函数重复命名的参数
  • 八进制数字直接量 (比如 010)
  • 重复属性名 ( ES5 会报错,ES6 不会)
  • 使用 implements, interface, let, package, private, protected, public, static 和 yield 作为标识符

ES6的模块的静态加载

  • 静态加载:在编译阶段进行加载(预编译)

    JS是脚本语言,是边执行边编译的,这里的编译阶段应该是指的执行前的这个编译解释阶段

  • 动态加载:在运行时加载依赖

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。commonjs和AMD模块,都只能在运行时确定这些东西。

  • 运行时加载: CommonJS/AMD 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”
  • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,输入时采用静态命令的形式。即在输入时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载

Commonjs和ES6模块的运行机制就像是深复制和浅复制

ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,ES6模块是动态引用。

  • 在 CommonJS 中,导入(imports)是模块导出值的复制值(同时 require()动作是同步的)。也就是说,在一个模块中一个值和这个值导出被复制后的值不是同一个,两者之间不存在连接
  • 在 ES6 中,导入是对导出值的只读。因此,模块中的值和导出后的值是同一个,存在连接,只是在导入的模块中对这个值是只读的。“只读”说明在导入的模块中不能直接修改被导入的值,如果要修改被导入的值,可以通过调用被导入模块的函数来达到目的。

静态模块结构的优势:

  1. 在打包时消除死代码(dead code)。在项目开发的过程中,我们可以借助 ES6 实现模块化开发;在部署的时候,可以把这些分散的模块集中打包集成(打包减少了网络请求的数量,这点并不时很关键,因为在 HTTP/2 中将会有所改变;打包压缩了代码,减少了代码的体积;在打包过程中,没有用到的代码将会被移除)。
  2. 简洁高效的打包,不存在定制的打包格式(compact bundling, no custom bundle format)。ES6 模块可以被高效地合并,是因为所有的模块都被当作一个单独的作用域(single scope)(通过重命名变量来消除名冲突)。这些得益于 ES6 模块的两个特性:
  • 静态模块结构不存在模块的条件加载(但是仍可以通过把模块放到函数中来实现);
  • 导入对到导出的值只读,这意味着我们无需复制导出的值,而直接访问导出的值。
  1. 更快的导入检索(faster lookup of imports):
  • 在 CommonJS 中,通过复制来导入,同时模块存在动态机制(导出、导入),因而在查找属性的时候更加慢;
  • ES6 模块时静态的,意味着在模块执行之前就已经知道导入的值是什么,可以优化访问。
  1. 变量检查。同样得益于 ES6 的静态模块结构,我们可以在执行前进行导入、导出的变量检查。
  2. 为宏指令做准备(ready for macros)。macros 会是 JavaScript 发展蓝图中的一个 milestone。目前可以通过一些第三方的库来实现宏定义。
  3. 静态类型(static types)定义.
  4. 静态结构为支持其他编译型语言提供了可能。