ES Module
浏览器中使用
html 中使用
在 html 中 script 标签添加 type="module",表示可以以 ES Module 的标准执行其中的 JS 代码 ESM
自动采用了严格模式,忽略 'use strict' 每个 ES Module 都是运行在单独的私有作用域中 ESM 可以通过 CORS 的方式请求外部的 JS
模块 ESM 的 script 可以添加 defer ,会延迟执行脚步,即页面加载完成后才会执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ES Module - 模块的特性</title>
</head>
<body>
<!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
<script type="module">
console.log('this is es module')
</script>
<!-- 1. ESM 自动采用严格模式,忽略 'use strict' -->
<script type="module">
console.log(this)
</script>
<!-- 2. 每个 ES Module 都是运行在单独的私有作用域中 -->
<script type="module">
var foo = 100
console.log(foo)
</script>
<script type="module">
console.log(foo)
</script>
<!-- 3. ESM 是通过 CORS 的方式请求外部 JS 模块的 -->
<!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->
<!-- 4. ESM 的 script 标签会延迟执行脚本 -->
<script defer src="demo.js"></script>
<p>需要显示的内容</p>
</body>
</html>
第二个 module 中 foo 无法获取到第一个 module 的值,表明作用域是分开的 script defer 标签下面有个 p 标签,页面显示了 p 标签的值才弹出对话框,表明脚步延迟执行
js 中导出
- 通过 export 单个导出属性、函数和类
// module.js
// 导出
export var name = 'foo module'
export function hello() {
console.log('hello')
}
export class Person {
}
// app.js
// 导入
import {name, hello, Person} from './module.js'
console.log(name, hello, Person)
- 通过 export 导出一组成员
// module.js
// 导出
var name = 'foo module'
function hello() {
console.log('hello')
}
class Person {
}
// 这种方式不是对象的方式,只是 export 的语法
export {name, hello, Person}
// app.js
// 导入
import {name, hello, Person} from './module.js'
console.log(name, hello, Person)
- 通过 export 导出默认值
// module.js
// 导出
var name = 'foo module'
export default name
// app.js
// 导入
// 通过默认值重命名
import {default as fooName} from './module.js'
console.log(fooName)
// 直接修改 default 的命名
import name1 from './module.js'
console.log(name1)
- 通过 export 导出,并且重命名
// module.js
// 导出
var name = 'foo module'
function hello() {
console.log('hello')
}
export {
// 重命名默认导出
name as default,
// 更换 hello 的命名
hello as fooHello
}
// app.js
// 导入
import name1, {fooHello} from './module.js'
console.log(name1, fooHello)
注意
在 CommonJS 中是先将整个模块导入为一个对象,然后从对象中解构出需要的成员
const { name, age } = require('./module.js')
在 ES Module 中 {} 是固定的语法,就是直接提取模块导出成员
import { name, age } from './module.js
在 ES Module 中导入成员并不是复制一个副本,而是直接导入模块成员的引用地址,也就是说 import 得到的变量与 export 导出的变量在内存中是同一块空间。一旦模块中成员修改了,导入的地方也会修改。
import {name, age} from './module.js'
setTimeout(() => {
console.log(name, age)
})
导入模块变量是只读的
import {name} from './module.js'
name = 'tom' // 报错
如果导入的是一个对象,对象的属性读写不受影响
name.xxx = xxx
export {} 不是一个对象字面量,只是语法上的规则而已
var name = 'jack'
var age = 18
export {name, age}
// 错误用法
// export name
// export 'foo'
js 中导入
// /test/module.js
var name = 'jack'
var age = 18
export {name, age}
console.log('module action')
export default 'default export'
// /test/utils/index.js
export function lowercase(input) {
return input.toLowerCase()
}
- 后缀不能省略
// from 后面需要跟文件的后缀,不能去除
import {name} from './module'
// 正确用法
import {name} from './module.js'
console.log(name)
- index.js 也不能省略
// 错误导入
import {lowercase} from 'utils'
// 正确导入
import {lowercase} from 'utils/index.js'
console.log(lowercase('HHH'))
- from 后面导入包内的文件不能省略 ./
// 这样导入会被认为是导入了第三方包,回去 node_modules 中找包
import {name} from 'module.js'
// ./ 不能省略
import {name} from './module.js'
// 或者使用项目为绝对路径的方式导入
import {name} from '/test/module.js'
// 也可以通过网络的方式导入包
import {name} from 'http://localhost:3000/test/module.js'
console.log(name)
- 只导包,不使用
import {} from './module.js'
import './module.js'
- 导出包中所有的内容
import * as mod from './module.js'
console.log(mod)
- 不能通过变量或者条件判断的方式导入包
错误导包
const modulePath = './module.js'
import {name} from 'modulePaht'
if (true) {
// 错误导入方式
// import {name} from './module.js'
}
- 通过 import() 函数动态导入包
import(),函数返回了一个 Promise,可以在 then 中接受到这个模块对象
import('./module.js').then(module => {
console.log(module)
})
- 提取包中的值和默认的值
import {name, age, default as title} from './module.js'
import abc, {name, age} from './module.js'
// abc 和 title 都是 default 的值
导入与导出
// components/avatar.js
export var Avatar = 'Avatar Component'
// components/button.js
var Button = 'Button Component'
export default Button
把两个组件合并到一个文件中进行导出
// components/index.js
import Button from './button.js'
import {Avatar} from './avatar.js'
export {Button, Avatar}
或者可以通过 export 直接导出
export {default as Button} from './button.js'
export {Avatar} from './avatar.js'
node 中使用
node 使用 ESM
在 node 中使用 esModule 需要将扩展名由 .js 改为 .mjs
启动的时候需要额外添加 --experimental-modules 参数
可以通过 ESM 的方式导入 node 的内置模块,内置模块兼容了 ESM 的提取成员的方式
import {foo, bar} from './module.mjs'
console.log(foo, bar)
// 可以通过 esm 加载内置模块
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员的方式
import {writeFileSync} from 'fs'
writeFileSync('./bar.txt', 'es module working')
// 对于第三方的 NPM 模块也可以通过 ESM 加载
import _ from 'lodash'
console.log(_.camelCase('ES Module'))
// 不支持,第三方模块都是导出默认成员
// import {camelCase} from 'lodash'
// console.log(camelCase('ES Module'))
esModule 中使用 commonJS
// es-module.mjs
import mod from './commonjs.js'
console.log(mod)
// { foo: 'commonjs exports value' }
// 不能从 CommonJS 中直接提取成员,import 不是解构导出对象
// 错误导出方式
import {foo} from './commonjs.js'
console.log(foo)
// commonjs.js
// CommonJS 模块始终只会导出一个默认值
module.exports = {
foo: 'commonjs exports value'
}
commonJS 中不能使用 esModule
// es-module.mjs
export const foo = 'es module export value'
// commonjs.js
// 报错,不能在 CommonJS 模块中通过 require 载入 ES Module
const mod = require('./es-module.mjs')
console.log(mod)
esModule 和 commonJS 的区别
ESM 中没有模块全局成员,以下变量全部报错
// esm.mjs
// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件夹的绝对路径
console.log(__filename)
// 当前文件所在的目录
console.log(__dirname)
ESM 中获取 __filename 和 __dirname
import.meta.url 中为 url 形式的文件路径,需要转为当前的绝对路径
import {fileURLToPath} from 'url'
import {dirname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
定义该项目为 ES Module
将 package.json 的 type 改为 module,这该项目所有的 js 文件都遵循 ES Module,不再需要 .mjs 后缀
CommonJS 的使用这需要改后缀为 .cjs
{
"type": "module"
}
// module.js
export const foo = 'hello'
export const bar = 'world'
// index.js
import {foo, bar} from './module.js'
import * as mod from './common.cjs'
console.log(mod)
console.log(foo, bar)
// common.cjs
const path = require('path')
console.log(path.join(__dirname, 'foo'))
module.exports = {
foo: 'hhh'
}