本篇目录
说明
ECMAScript 6 入门:Module 的语法中介绍,JavaScript 一直是没有 module 功能的,大型项目无法分拆成多个模块实现。在 ES6 之前,CommonJS 和 AMD 分别实现了服务端的模块加载方案和浏览器端的模块加载方案。ES6 在语言标准上支持的模块功能,是浏览器和服务端通用的模块解决方案。
模块
ECMAScript 6 中,一个模块就是一个 js 文件。
需要特别注意的是:ES6 的模块全部都是严格模式,无论是否在文件头添加了“use strict”。
严格模式是 ES5 引进的,主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
export、import 用于模块的导出导入,这两个指令最先执行,是静态加载,必须在模块的顶层。
Node 中的 require 方法是动态加载,和 export、import 不同:
const path = './' + fileName;
const myModual = require(path);
ECMAScript 6 有一个提案,用 import() 实现动态加载。
export 指令
export 在模块内指定可以导出的变量,只有用 export 指定的变量可以在模块外使用。
export 可以在模块顶层
的任何位置使用,不能在块级作用域(函数内部、条件代码块中等)中。
两种写法
export 写法1:
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
export 写法2:
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
函数和类的导出
函数的导出:
export function multiply(x, y) {
return x * y;
};
重命名导出
可以用 as
将变量重命名,导出为其它的名字:
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
导出值是动态绑定的
这一点需要特别注意:export 导出的变量,从模块外获取的是它在模块的内的实时值。
例如下面的模块,导出的变量 foo 的初始值是 bar
,在模块内用 setTimeout 设置超时处理,500 毫秒后,编程 baz
:
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
在模块外使用的 foo,拥有同样的特性,即在 500 毫秒后,值变为 baz
。
import 指令
import 用于在当前模块中导入另外一个模块,导入的变量可以在当前模块中直接使用。
import { firstName, lastName, year } from './profile.js';
可以用 as 重命名导入的变量:
import { lastName as surname } from './profile.js';
from 后面的模块文件,可使用相对路径和绝对路径,后缀 .js
可以省略。
也可以不带路径,直接使用模块名,但是这样需要提前在配置文件告知 JavaScript 的模块的位置:
import {myMethod} from 'util';
导入的变量都是只读的
不能修改导入的变量的值,即使导入变量的属性能够修改,也不应修改。
直接修改导入的变量是语法错误:
import {a} from './xxx.js';
a = {}; // Syntax Error : 'a' is read-only;
导入变量的属性是可以修改的,但是非常危险!因为其它导入了这个模块的模块,也会看到修改后的属性!这样会使被导入的模块的实现不可控、不可靠!
import {a} from './xxx.js';
a.foo = 'hello'; // 合法操作,但不应该这样做!
只加载执行
可以只加载执行导入的模块:
import 'lodash';
一个模块如果被重复导入多次,只会加载执行一次。
export、import 混合使用
在一些场景下,export 和 import 可以混合使用。
模块整体导入
可以将一个模块整体加载,然后用 .
操作符引用模块的导出变量。
例如下面这个模块:
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
整体导入的使用方式:
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
导入的变量同样是只读的,不能改变。
模块的默认导出
export default
指定模块的默认导出,一个模块只能有一个默认导出,因此 export default 在模块内只能用一次。
export default 的真实含义是定义了名为 default 的变量,并将其导出。
设置了默认导出的模块:
// export-default.js
export default function () {
console.log('foo');
}
导入方法,customName 默认导出的 default 变量的新名字,不需要包含在 {}
中:
// import-default.js
import customName from './export-default';
customName(); // 'foo'
将另一个模块中的变量导出
可以在当前模块中导入另一个模块中的变量,然后作为本模块的导出:
export { foo, bar } from 'my_module';
等同于:
import { foo, bar } from 'my_module';
export { foo, bar };
默认导出的再次导出:
export { default } from 'foo';
还可以将一个模块整体导入后,再整体导出,形成类似继承的关系,被继承的模块的默认导出被 export *
忽略:
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
在 Node 中使用 import
Node 12 对 import 还不是默认支持的,直接用 node 运行使用了 import 的 js 文件会报下面的错误:
import module1 from './module1.js'
^^^^^^^
SyntaxError: Unexpected identifier
at Module._compile (internal/modules/cjs/loader.js:718:23)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:785:10)
at Module.load (internal/modules/cjs/loader.js:641:32)
at Function.Module._load (internal/modules/cjs/loader.js:556:12)
at Function.Module.runMain (internal/modules/cjs/loader.js:837:10)
at internal/main/run_main_module.js:17:11
要解决这个问题,可以用 bazel 引导启用,或者使用 node 9 引入的在 node 12 时依然是实验状态的特性:--experimental-modules
,这两种方法都有额外影响,如非必须直接使用 node 的 require 方法。
方法1: 用 bazel 引导
安装 babel 包:
npm install babel-register babel-preset-env --D
然后编写一个新的 js 文件作为程序的执行入口,用下面的方法加载要运行的模块:
require('babel-register') ({
presets: [ 'env' ]
})
module.exports = require('./app.js')
文件结构如下:
├── app.js
├── install.sh
├── module1.js
├── run.sh
└── start.js
运行:
node ./start.js
方法2: –experimental-modules
截止 Node 12,–experimental-modules 是 node 的试验特性,需要在 node 运行的时候指定,并且 js 文件的后缀要修改成 .mjs:
.
├── app.mjs
├── module1.mjs
└── run.sh
引用时也要使用 .mjs 后缀:
import module1 from './module1.mjs'
运行时,指定参数:
node --experimental-modules ./app.mjs
参考
作者:李佶澳 更新时间:2019-06-22T19:01:33+0800