ES6模块 intro
在ES6之前,实现模块化使用的是RequireJS
或seaJS
(分别是基于AMD
规范的模块化库,和基于CMD
规范的模块化库) 见后“JS的模块化书写规范”。
ES6中引入了模块化,其设计思想是 在编译时就能确定模块的依赖关系及输出和输入的变量。
ES6的模块化分为导入和导出(export
import
)。
- ES6模块 details
- 自动开启严格模式。
- 模块中可以导入|导出各种类型的变量(如对象,函数,字符串,数值,布尔值,类等)。
- 每个模块有自己的上下文
context
,每一个模块内声明的变量是局部变量,不会污染到全局作用域。 - 每个模块只加载一次(
单例
)。对同一文件之后的加载,直接从内存中读取。
export
与import
-
基本用法
模块可以导入导出各种类型的变量,如字符串,数值,函数,类。- 导出的函数声明与类声明必须要有名称(
export default
命令再说) - 可以导出声明,也可以导出引用(如函数的引用)
export
命令可以出现在模块的任何位置,但必须在模块顶层import
命令会提升到整个模块的头部,首先执行。
// a.js var myName = "JT"; var myFunc = function() { return `name = ${myName}`; } var myClass = class MyClass { static msg = "hello 测试"; } export {myNAme, myFunc, myClass}; // b.js import {myName, myFunc, myClass} from "./a.js"; alert(myName); alert(myFunc()); alert(myClass.msg);
- 导出的函数声明与类声明必须要有名称(
-
as
- 模块中导出的接口可以在
export
中用as
重命名。
// a.js var myName = "JT"; export {myName as exportName}; // b.js import {exportName} from "./a.js"; console.log(exportName);
- 多个模块中导出了重名的接口,可以在
import
中用as
重命名。
// a1.js var myName = "a1"; export {myName}; // a2.js var myName = "a2"; export {myName}; // b.js import {myName as name1} from "./a1.js"; import {myName as name2} from "./a2.js"; console.log(name1, name2);
- 模块中导出的接口可以在
-
import
- 只读属性:不能改引用的值,但可以改写引用指向的对象的属性的值。
import {a} from "./xxx.js"; // a = {}; // error,不能直接修改引用的指向。 a.foo = "hello"; // 可以改对象的属性值。
- 单例模式:对同一文件,就算有多条
import
指令,但只执行一次import
。
import {a} from "./xxx.js"; // 只执行一次 import {a} from "./xxx.js"; import {a} from "./xxx.js"; import {b} from "./xxx.js"; // 相当于 import {a, b} from "./xxx.js";
- 静态执行:
import
是静态执行,所以不能使用表达式和变量。
import {"f" + "oo"} from "methods"; // error var module = "methods"; import {foo} from module; // error if (true) { // error import {foo} from "method1"; } else { import {foo} from "method2"; }
-
export default
使用export default
导出默认的变量,这个变量可以用任意变量来接收。- 在一个文件(模块)中,
export
和import
可以有多个,但是export default
只有一个。 export default
中的default
是对应的导出接口变量。- 通过
export
方式导出,在导入时要加{}
,export default
不需要大括号。 export default
向外暴露的成员,可以使用任意变量来接收。
var a = "hello test"; export default a; // 只有一个 import b from "./xxx.js"; // 不需要加{},使用任意变量接收。
- 在一个文件(模块)中,
-
复合使用
export
和import
可以在同一模块使用- 可以将导出接口改名,包括
default
。 - 可以导出全部
- 当前模块导出的接口会覆盖继承导出的。
export {foo, bar} from "methods"; // import {foo, bar} from "methods"; export {foo, bar}; // 重命名接口 export {foo as bar} from "methods"; export {foo as default} from "methods"; export {default as foo} from "methods"; export * from "methods";
- 可以将导出接口改名,包括
JS的模块化书写规范
CommonJS
CommonJS
规范诞生较早。NodeJS
就采用了CommonJS
。
var clock = require("clock");
clock.start();
以上为加载模块的方式,这种写法适合服务端(服务端是在本地磁盘读取模块)。
- CommonJS details
- 一个单独的文件就是一个模块
- 用
require()
方法加载模块(该方法读取一个文件并执行,最后返回文件内部的exports
对象)。 require()
方法默认读取JS文件,所以.js
后缀可省略。
但如果是在客户端的浏览器中加载远程(服务器中)的模块,(同步加载)可能会“假死”,需要能异步加载模块的方式。
于是有了AMD
和CMD
方案。
AMD
(Asynchronous Module Definition) 异步的模块定义
requireJS
应用了AMD规范
:先定义所有依赖,然后在加载完成后的callback中执行
语法:require([module], callback);
,如:
require(["clock"], function(clock) {
clock.start();
});
- AMD的模块支持:对象,函数,构造器,字符串,JSON等各种类型。
AMD
虽然实现了异步加载,但是它刚开始就把所有依赖写出来,这不符合书写的逻辑顺序。
所以我们又有了新的需求:
我们需要可以像CommonJS
那样在使用时在require
,并且支持 异步加载后再执行。所以就出现了CMD
,他不仅也异步,而且更通用。
CMD
(Common Module Definition) 通用的模块定义
CMD
是seajs
推崇的规范。其写法如下:
define(function(require, exports, module) {
var clock = require("clock");
clock.start();
});
AMD
和CMD
- same: 异步加载模块
- diff:
- 对依赖模块的执行时机处理不同
ADM
依赖前置 好处:JS可以方便知道有哪些依赖,立即加载。CMD
就近依赖 缺点:需要把模块变为字符串解析以便才知道所依赖的模块。
典型的牺牲运行时间(解析模块的用时),加快开发速度
- 职责单一
AMD
严格区分推崇职责单一,中的require
分全局和局部。CMD
中没有全局的require
,提供seajs.use()
实现模块的加载启动。
- 对依赖模块的执行时机处理不同