概述
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。‘
特点
每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;
每一个模块内声明的变量都是局部变量, 不会污染全局作用域;
模块内部的变量或者函数可以通过export导出;
一个模块可以导入别的模块;
ES6的模块自动采用严格模式。
功能简介
模块功能主要由两个命令构成 export和import。
export命令用于规定模块的对外接口。import命令用于输入其他模块提供的功能。
export命令
一个模块就是一个独立的文件。该文件内部的所有的变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出它。
1. 最基本的用法
//profile.js
//输出变量:
export var year = 1958;
//或者写成:
var year = 1958;
export {year}; (推荐)
//输出函数:
export function f() {};
//或者写成:
function f() {}
export {f};
2. as关键字
重命名对外的接口
function v1(){.....}
function v2(){.....}
export{
v1 as streamV1,
v2 as streamV2
}
3. export语句输出的值与对应的值是动态绑定关系,即通过该接口可以取到模块内部实时的值
export var foo="foo";
setTimeout(()=>foo='baz',500);
//输出变量foo,值为bar,500ms后变成baz
4. 错误写法
export命令规定的是对外的接口,实质是在接口名与模块内部变量之间建立了一一对应的关系。
export {foo,baz}
//等价于
export {foo:foo,baz:baz}
针对与上面一点,下面的写法就会导致错误
export 1;
var m=1;
export m;
import命令
使用export命令定义了模块的对外接口以后,其他的js文件就可以通过import命令加载这个模块了。
1. 基本用法
//main.js
import {year} from './profile';
function setName(element){
element.textContent=year;
}
大括号{}中的变量名必须与被导入模块对外接口的名称相同。
2. as用法
为输入的变量名重新取一个名字
import {year as num} from './profile';
3. from后面的文件位置
- 可以是相对路径,也可以是绝对路径;
.js后缀可以省略;- 如果只是模块名,不带有路径,那么必须有配置文件告诉Javascript引擎该模块的位置。
4. import是静态执行
import命令是编译阶段执行的,在代码运行之前。
由于import是静态执行,所以不能使用表岛是和变量,只有在运行时才能得到结果的语法解构。
//报错
import {'fo'+'oo'} from 'my_moudule';
//报错
let module='my_module';
import {foo} from module;
模块的整体加载(*)
上面我们代码中涉及到的是指定加载某个输入值,还可以使用整体加载(即型号)来指定一个对象,*所有输出值**都加载在这个对象上。
circle.js文件
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
main.js文件
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上面写法是逐一指定要加载的方法,整体加载的写法如下。
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default 命令
从前面的例子可以看出,使用import命令时用户需要知道所加载的变量名或者函数名,否则无法加载。
为了方便用户,使其不用阅读文档就能加载模块,可以使用export default命令为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面的import命令可以用任意名称指向文件的输出的方法,这时就不需要知道原模块输出的函数名了。
需要注意的是 这时import命令后面不使用大括号。
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以import命令后面不用加大括号,因为只可能对应一个方法。
export与import的复合写法
用于在一个模块之中先输入后输出同一模块。
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, boo};
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
模块的继承
模块之间也可以继承
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
注意,
export *命令会忽略circle模块的default方法。