历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
严格模式(ES5)
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
严格模式主要有以下限制。
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)
export命令
export命令用于规定模块的对外接口。
1、一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
// 输出变量
//a.js
var firstName = 'yue';
var lastName = 'zhao';
var year = 2001;
export { firstName, lastName, year };
写法二:(不推荐,太复杂)
export var firstName = 'yue';
export var lastName = 'zhao';
export var year = 2001;
输出函数或类(class)。
export function multiply(x, y) {
return x * y;
};
2、通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
3、export命令规定的是对外的接口,必须与模块内部的变量建立一 一对应关系。
// 直接输出 1,报错
export 1;
// 通过变量m,还是直接输出 1。1只是一个值,不是接口,报错
var m = 1;
export m;
正确写法:
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
function和class的输出,也必须遵守这样的写法。
4、export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 100);
上面代码输出变量foo,值为bar,100 毫秒之后变成baz。
import 命令
import用于在一个模块中加载另一个含有export接口的模块。
// b.js(设a、b在同一个目录下)
import { firstName, lastName, year } from './a.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
1、import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
a是一个只读的接口。如果a是一个对象,改写a的属性是允许的。
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。
2、由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。
export default 命令
指定模块的默认输出,一个模块只能有一个默认输出。
// export-default.js
export default function () {
console.log('foo');
}
它的默认输出是一个函数。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。
2、比较一下默认输出和正常输出。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。
因为export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。
3、export default就是输出一个叫做default的变量或方法
(1)系统允许你为它取任意名字
(2)它后面不能跟变量声明语句
// 正确
export var a = 1;
// 正确,export default a的含义是将变量a的值赋给变量default。
var a = 1;
export default a;
// 错误
export default var a = 1;
(3)可以直接将一个值写在export default之后
// 正确,指定对外接口为default。
export default 42;
// 报错,没有指定对外的接口
export 42;
export、import与export default区别
在JavaScript ES6中,export与export default均可用于导出常量、函数、文件、模块等,你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用,但在一个文件或模块中,export、import可以有多个,export default仅有一个。
具体使用:
1、
//demo1.js
export const str = 'hello world'
export function f(a){
return a+1
}
对应的导入方式:
//demo2.js
import { str, f } from 'demo1' //也可以分开写两次,导入的时候带花括号
2、
//demo1.js
export default const str = 'hello world'
对应的导入方式:
//demo2.js
import str from 'demo1' //导入的时候没有花括号
export 与 import 的复合写法
1、如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
2、模块的接口改名和整体输出,也可以采用这种写法。
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
3、默认接口的写法如下。
export { default } from 'foo';
4、具名接口改为默认接口的写法如下。
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
import()函数
1、ES2020提案 引入import()函数,支持动态加载模块。
import(specifier)
上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。
2、import()返回一个 Promise 对象。
3、import()的一些适用场合。
(1)按需加载。
import()可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。
(2)条件加载
import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。
(3)动态的模块路径
import()允许模块路径动态生成。
import(f())
.then(...);
上面代码中,根据函数f的返回结果,加载不同的模块。