Appearance
module 语法
概述
ES6 之前,最主要模块加载方案 为 CommonJS 和 AMD 。前者用于服务器,后者用于浏览器。
ES6 模块设计思想:尽量静态化,使得编译时就能确定模块的依赖关系。而 CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
CommonJS 模块就是对象,输入时必须查找对象属性
js
// CommonJS模块
let { stat, exists, readfile } = require('fs')
// 等同于
let _fs = require('fs')
let stat = _fs.stat
let exists = _fs.exists
let readfile = _fs.readfile整体加载 fs 模块,生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,没办法在编译时做“静态优化”
ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。
js
// ES6模块
import { stat, exists, readFile } from 'fs'只加载 3 个方法,其他方法不加载。这种加载称为 编译时加载 或者 静态加载,可以在编译时完成,所以可以做静态分析。
引入宏(macro)和类型检验(type system)等静态分析。
ES6 模块还有以下好处
不需要 UMD 模块格式 ,服务器浏览器格式统一
新 API 通过 export 提供,不必做成变量,挂在到全局变量
不再需要对象作为命名空间,如
Math对象
严格模式
ES6 模块自动采用严格模式
严格模式限制
- 变量必须声明后再使用
- 函数的参数不能有同名属性
- 不能使用 with 语句
- 不能对只读属性赋值
- 不能使用前缀 0 表示八进制数
- 不能删除不可删除的属性
- 不能删除变量 delete prop,会报错,只能删除属性 delete global[prop]
- eval 不会在它的外层作用域引入变量
- eval 和 arguments 不能被重新赋值
- arguments 不会自动反映函数参数的变化
- 不能使用 arguments.callee
- 不能使用 arguments.caller
- 禁止 this 指向全局对象
- 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
- 增加了保留字(比如 protected、static 和 interface)
- ES6 模块之中,顶层的 this 指向 undefined,即不应该在顶层代码使用 this
export 命令
模块功能由两个命令构成:export 和 import
export:规定模块的对外接口
import:输入其他模块提供的功能
export 输出变量
js
// profile.js
export var firstName = 'Michael'
export var lastName = 'Jackson'
export var year = 1958js
// profile.js
var firstName = 'Michael'
var lastName = 'Jackson'
var year = 1958
export { firstName, lastName, year }export 输出函数或类
js
export function multiply(x, y) {
return x * y
}用 as 关键字重命名
js
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};可以多次重命名
export 必须与模块内部的变量建立一一对应关系。即导出的变量对应一个值,而不是直接导出一个值
错误写法:
js
// 报错
export 1;
// 报错
var m = 1;
export m;第一种写法:直接输出 1
第二种写法:通过变量 m,还是直接输出 1
正确写法:
js
// 写法一
export var m = 1
// 写法二
var m = 1
export { m }
// 写法三
var n = 1
export { n as m }function 和 class 的输出,也必须遵守这样的写法
js
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};export 导出的值是动态绑定,可以 通过 import 获取实时数据
js
export var foo = 'bar'
setTimeout(() => (foo = 'baz'), 500)上面代码输出变量foo,值为bar,500 毫秒之后变成baz
注:CommonJS 模块输出是值的缓存,不存在动态更新。
export 可以出现在任何位置,但需保证为顶层模块。处于条件代码中,无法做静态优化。
js
function foo() {
export default 'bar' // SyntaxError
}import 命令
通过 import 加载 export 导出的模块
as 关键字:将输入变量重命名
js
import { lastName as surname } from './profile.js'import 命令输入的变量都是只读的
js
import { a } from './xxx.js'
a = {} // Syntax Error : 'a' is read-only;如果 a 为对象,对象属性可以被修改
import 命令中 from 后路径表示文件位置: 相对路径 或 绝对路径
不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
js
import { myMethod } from 'util'import 命令会自动提升到整个模块的头部,首先执行。因为 import 是编译阶段执行,代码运行之前。
import 是静态执行,所以不能使用表达式和变量
js
// 报错
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';
}import 语句会执行所加载的模块
js
import 'lodash'多次重复执行同一句 import 语句,那么只会执行一次
js
import 'lodash'
import 'lodash'js
import { foo } from 'my_module'
import { bar } from 'my_module'
// 等同于
import { foo, bar } from 'my_module'模块整体加载
整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面
js
// circle.js
export function area(radius) {
return Math.PI * radius * radius
}
export function circumference(radius) {
return 2 * Math.PI * radius
}js
import * as circle from './circle'
console.log('圆面积:' + circle.area(4))
console.log('圆周长:' + circle.circumference(14))注意:模块整体加载所在的那个对象(上例是 circle),是可以静态分析,所以不允许运行时改变。
export default 命令
import 命令,需要知道所要加载的变量名或函数名,否则无法加载
export default 命令:为模块指定默认输出。其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字
js
// export-default.js
export default function () {
console.log('foo')
}
// import-default.js
import customName from './export-default'
customName() // 'foo'export default 命令用在非匿名函数
js
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;默认输出和正常输出区别
js
// 第一组
export default function crc32() {
// 输出
// ...
}
import crc32 from 'crc32' // 输入
// 第二组
export function crc32() {
// 输出
// ...
}
import { crc32 } from 'crc32' // 输入注:一个模块只能有一个默认输出,export default 命令只能使用一次
export default 本质是输出名为 default 的变量。也就是将输出的值,赋值给名为 default 的变量
js
// modules.js
function add(x, y) {
return x * y
}
export { add as default }
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules'
// 等同于
// import foo from 'modules';export default 输出类
js
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();注:通过 require 处理 ES6 模块,default 属性才是具体值
js
const a = require('./d').default()export 和 import 复合写法
先输入模块,再将模块原样输出
js
export { foo, bar } from 'my_module'
// 等同于
import { foo, bar } from 'my_module'
export { foo, bar }注:上述写法是对外转发接口,当前模块是无法取到值的。即 foo,bar 无法直接使用。
模块的接口改名和整体输出
js
// 接口改名
export { foo as myFoo } from 'my_module'
// 整体输出
export * from 'my_module'默认接口的写法
js
export { default } from 'foo'默认接口改具名接口
js
export { default as es6 } from './someModule'
// 等同于
import es6 from './someModule'
export { es6 }具名接口改为默认接口
js
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;ES2020 之前,有一种 import 语句,没有对应的复合写法
js
import * as someIdentifier from 'someModule'ES2020 补上了这个写法
js
export * as ns from 'mod'
// 等同于
import * as ns from 'mod'
export { ns }模块的继承
模块之间也可以继承
js
// circleplus.js
export * from 'circle'
export var e = 2.71828182846
export default function (x) {
return Math.exp(x)
}js
// main.js
import * as math from 'circleplus'
import exp from 'circleplus'
console.log(exp(math.e))export *命令会忽略 circle 模块的 default 方法
解决:
js
export * from 'circle'
export { default as _default } from 'circle'跨模块常量
const 声明的常量只在当前代码块有效
设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享
js
// constants.js 模块
export const A = 1
export const B = 3
export const C = 4
// test1.js 模块
import * as constants from './constants'
console.log(constants.A) // 1
console.log(constants.B) // 3
// test2.js 模块
import { A, B } from './constants'
console.log(A) // 1
console.log(B) // 3import()
问题:import 和 export 命令只能在模块的顶层,不能在代码块之中。但 require 是动态加载,所以 import 无法取代 require 的动态加载功能
js
const path = './' + fileName
const myModual = require(path)简介
ES2020 提案 引入 import()函数,支持动态加载模块
import 命令能够接受什么参数,import()函数就能接受什么参数。import()返回一个 Promise 对象
js
const main = document.querySelector('main')
import(`./section-modules/${someVariable}.js`)
.then((module) => {
module.loadPageInto(main)
})
.catch((err) => {
main.textContent = err.message
})import()函数是可以用在任何地方。与 require()方法类似,但前者为异步加载,后者为同步加载
js
async function renderWidget() {
const container = document.getElementById('widget')
if (container !== null) {
// 等同于
// import("./widget").then(widget => {
// widget.render(container);
// });
const widget = await import('./widget.js')
widget.render(container)
}
}
renderWidget()适用场合
import()适用场合
- 按需加载
import()可以在需要的时候,再加载某个模块。
- 条件加载
import()可以放在 if 代码块,根据不同的情况,加载不同的模块
- 动态的模块路径
import()允许模块路径动态生成
注意点
import()加载模块成功以后,这个模块会作为一个对象,当作 then 方法的参数
js
import('./myModule.js').then((myModule) => {
console.log(myModule.default)
})模块有 default 输出接口
js
import('./myModule.js').then((myModule) => {
console.log(myModule.default)
})同时加载多个模块
js
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});import()在 async 函数使用
js
async function main() {
const myModule = await import('./myModule.js')
const { export1, export2 } = await import('./myModule.js')
const [module1, module2, module3] = await Promise.all([import('./module1.js'), import('./module2.js'), import('./module3.js')])
}
main()