ECMA组织参考了众多社区模块化标准,在2015年发布了官方的模块化标准。这也是JS首次在语言标准的层面上实现了模块功能,逐步取代了之前的CommonJS和AMD规范,成为浏览器通用的解决方案。
ECMAScript 6模块的设计思想是尽量静态化,使编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块都只能在运行时确定这些依赖关系及变量。例如CommonJS模块就是对象,输入时必须查找对象属性。
ECMAScript 6模块化的特点如下:
(1)一个ECMAScript 6的模块就是一个JS文件。
(2)模块只会加载和执行一次,如果下次再加载同一个文件,则直接从内存中读取。
(3)一个模块就是一个单例对象。
(4)模块内声明的变量都是局部变量,不会污染全局作用域。
(5)模块内部的变量或者函数可以通过export导出。
(6)模块与模块直接可以相互依赖和相互引用。
模块化实现了把一个复杂的系统分成各个独立的功能单元,每个单元可以独立设计和自由组合,模块化的优点如下:
(1)减少命名冲突。
(2)避免引入时的层层依赖。
(3)可以提升执行效率。
(4)架构清晰,可灵活开发。
(5)降低耦合,可维护性高。
(6)方便模块功能调试、升级及模块间的组合拆分。
模块化也存在一些缺点:
(1)损耗性能。
(2)系统分层,调用链长。
(3)目前浏览器无法使用import导入模块,需要第三方打包工具的支持。
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,如果希望外部能够读取模块内部的某个变量,就必须使用关键字export输出该变量。
(1)export:用于规定模块的对外接口。
(2)import:用于输入其他模块提供的功能。
当定义好一个模块后,默认情况下,模块内部的内容在模块外不能直接访问,因此需要在模块中通过export关键字指定哪些内容是对外的,没有添加export关键字的内容是私有的,ES6中提供了多种模块内容的导出方式。
导出多个函数或者变量,一般使用场景例如utils、tools、common之类的工具类函数集,或者全站统一变量等。
导出时只需要在变量或函数前面加export关键字,如代码示例2-168所示。
代码示例2-168 chapter02\es6_demo\14-module\main.js
调用模块,执行后的输出结果如图2-2所示。
图2-2 调用libs.js模块的执行结果
也可以直接导出一个列表,例如上面的libs.js可以改写,如代码示例2-169所示。
代码示例2-169 chapter02\es6_demo\14-module\libs.js
导出一个默认函数或者类,这种方式比较简单,一般用于一个类文件,或者功能比较单一的函数文件。一个模块中只能有一个export default默认输出。
export default与export的主要区别有两个:
(1)不需要知道导出的具体变量名。
(2)导入(import)时不需要{}。
如代码示例2-170所示。
代码示例2-170 myFunc.js
导出一个类,如代码示例2-171所示。
代码示例2-171 MyClass.js
注意这里的默认导出不需要用{}。
混合导出,也就是前面介绍的第1种和第2种方式结合在一起的情况。例如Lodash之类的库采用这种组合方式,如代码示例2-172所示。
代码示例2-172 common.js
再例如lodash例子,如代码示例2-173所示。
代码示例2-173 lodash.js
一般情况下,export输出的变量就是在原文件中定义的名字,但也可以用as关键字来指定别名,这样做一般是为了简化或者语义化export的函数名,如代码示例2-174所示。
代码示例2-174 num.js
有时候为了避免上层模块导入太多的模块,可能使用底层模块作为中转,直接导出另一个模块的内容,如代码示例2-175所示。
代码示例2-175 myFunc.js
export只支持在最外层静态导出,并且只支持导出变量、函数、类,如下的几种用法都是错误的,如代码示例2-176所示。
代码示例2-176
import的用法和export的用法是一一对应的,但是import支持静态导入和动态导入两种方式,动态导入在兼容性上要差一些,目前仅Chrome浏览器和Safari浏览器支持。
当export有多个函数或变量时,如导出多个内容,可以使用∗as关键字来导出所有函数及变量,同时as后面跟着的名称作为该模块的命名空间,如代码示例2-177所示。
代码示例2-177
从模块文件中导入单个或多个函数,与∗as namepage方式不同,这个是按需导入。如代码示例2-178所示。
代码示例2-178
和export一样,也可以用as关键字设置别名,当导入的两个类的名字一样时,可以使用as来重设导入模块的名字,也可以用as来简化名称。如代码示例2-179所示。
代码示例2-179
有时候只想导入进来,不需要调用,这种情况很常见,例如在用Webpack构建时,经常导入.css文件,或者导入一个类库,如代码示例2-180所示。
代码示例2-180
静态import在首次加载时会把全部模块资源下载下来,但是,在实际开发时,有时候需要动态导入(dynamic import),例如当单击某个选项卡时才去加载某些新的模块,这个动态导入的特性浏览器也是支持的,如代码示例2-181所示。
代码示例2-181
ES 7的新用法,如代码示例2-182所示。
代码示例2-182
当浏览器加载ES 6模块时也使用<script>标签,但是需要加入type="module"属性,代码如下:
上面的代码用于在网页中插入一个模块utils.js,由于type属性被设为module,所以浏览器才可以识别出是ES 6模块。浏览器对于带有type=module的脚本采用异步加载,不会堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了script标签的defer属性,代码如下:
如果网页有多个<script type="module">,则它们会按照在页面出现的顺序依次执行。
<script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染而立即执行。执行完成后,再恢复渲染,代码如下:
一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成就执行该模块。
ES 6模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致,代码如下: