一、ES Module基本特性
<!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 模块的 -->
<!-- 不支持CORS-->
<!-- <script type="module" src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> -->
<!-- 支持CORS-->
<script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<!-- 4. ESM 的 script 标签会延迟执行脚本 = defer属性 -->
<script type="module" src="demo.js"></script>
<p>需要显示的内容</p>
</body>
</html>
二、导出
- 单独导出
export var name = 'foo module'
export function hello () {
console.log('hello')
}
export class Person {}
import { name } from './module.js'
import { hello } from './module.js'
import { Person } from './module.js'
- 合并导出
export { name, hello, Person }
import { name, hello, Person } from './module.js'
- 别名
// 以default方式导出, import的时候必须使用别名
export {
name as default,
hello as fooHello,
Person
}
// default使用的话必须设置别名,foolHello和Person可以不用设置
// 别名可以叫任意名字
// import { default as name, fooHello as hello, Person } from './module.js'
import name, { fooHello as hello, Person } from './module.js'
三、导出注意事项
1. 对象后面的花括号和export后的花括号是不同的概念:一个是【对象】,一个是导出【引用】。
比如,这两个花括号是不一样的:
var obj = { name, age } // 这个花括号是个对象
export { name, age } // 这个花括号是个引用
错误:
export name // 错误的导出用法
export 'foo' // 同样错误的导出用法
可以这样导出变量名
export default name
2. 导出导入的是引用的内存地址
导出:module.js:
var name = 'jack'
var age = 18
export { name, age }
setTimeout(function () {
name = 'ben'
}, 1000)
导入:app.js:
import { name, age } from './module.js'
console.log(name, age)
// 导入成员并不是复制一个副本,
// 而是直接导入模块成员的引用地址,
// 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。
// 一旦模块中成员修改了,这里也会同时修改,
setTimeout(function () {
console.log(name, age)
}, 1500)
// 输出 jack 18
3. 导入的变量只读不能修改
import { name, age } from './module.js'
name=1 //Uncaught TypeError: Assignment to constant variable.=
四、导入用法
// 一、文件导入的方法
import { name } from './module' // 1.不能省略.js,这点跟ComminJs是不一样的
import { name } from './module.js'
console.log(name)
import { lowercase } from './utils' // 2.不能只导入目录就可以找到index.js,这跟CommonJS是不一样的
import { lowercase } from './utils/index.js'
console.log(lowercase('HHH'))
import { name } from 'module.js' //3.不能以字母开头,因为ES Module就以为在加载第三方模块,而不是本地路径模块了,这点和CommonJS是相同的
import { name } from './module.js'
import { name } from '/04-import/module.js' // 4.可以用绝对路线省略/前的• → 根目录就是当前index.html目录
import { name } from 'http://localhost:3000/04-import/module.js'
console.log(name)
// 二、仅仅导入模块执行,而不需要使用对象
import {} from './module.js'
import './module.js'
// 三、把模块下所有的export属性都导出来,可以用 *
import * as mod from './module.js' //
console.log(mod)
// 四、2个不能 → 解决方案
var modulePath = './module.js' //1.不能将导入模块赋值给一个变量
import { name } from modulePath // 这才是正确导入写法
console.log(name)
// 2.不能嵌套在一个if、while语句中,只能在最顶层就导出模块
if (true) {
import { name } from './module.js'
}
// 3.解决方案:全局动态的导入方案,通过then拿到这个模块。
import('./module.js').then(function (module) {
console.log(module)
})
// 五、提取默认成员
// 1.复杂:默认成员导出方式
import { name, age, default as title } from './module.js'
// 2.简写:逗号左边用来提取默认成员,逗号右边用来提取具名成员。
import abc, { name, age } from './module.js'
console.log(name, age, abc)
五、多文件导入用法
// 在文件夹中新建一个index.js文件, 然后把文件夹中需要导出的模块都列出来
export { default as Button } from './button.js'
export { Avatar } from './avatar.js'
// 在需要倒入的文件中一次导入
import { Button, Avatar } from './components/index.js'
六、polyfill(正式环境不使用)
<!-- https://github.com/ModuleLoader/browser-es-module-loader -->
<!-- browser-es-module-loader导入不识别的文件给babel去解析-->
<!-- nomodule 只有在不识别的时候才引入-->
<script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
七、Node中使用ES Modules
从node 8.5后就支持Node环境中是ES Modules。具体做法就是把js的后缀名改成mjs
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
// 对于第三方的 NPM 模块也可以通过 esm 加载
// import _ from 'lodash'
// _.camelCase('ES Module')
// // 不支持,因为第三方模块都是导出默认成员
// // import { camelCase } from 'lodash'
// // console.log(camelCase('ES Module'))
Node v12 之后的版本,可以通过 package.json 中添加 type 字段为 module,
将默认模块系统修改为 ES Module,此时就不需要修改文件扩展名为 .mjs 了。
但是如果你想使用CommonJS,那么js后缀名要修改位cjs。
八、ES Modules 和 CommonJS 交互
// CommonJS 模块始终只会导出一个默认成员
// module.exports = {
// foo: 'commonjs exports value'
// }
exports.foo = 'commonjs exports value'
// ES Module 中可以导入 CommonJS 模块
import mod from './commonjs.js'
console.log(mod)
// 不能直接提取成员,注意 import 不是解构导出对象
// import { foo } from './commonjs.js'
// ES Module
export const foo = 'es module export value'
// 不能在 CommonJS 模块中通过 require 载入 ES Module
// const mod = require('./es-module.mjs')
image.png
九、ES Modules 和 CommonJS 不同点
CommonJS中可以有下面5个对象:
// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件的绝对路径
console.log(__filename)
// 当前文件所在目录
console.log(__dirname)
但是对应ES Modules中没有了, 可以按照下面方式替代:
// require, module, exports 自然是通过 import 和 export 代替
// __filename 和 __dirname 通过 import 对象的 meta 属性获取
// const currentUrl = import.meta.url
// console.log(currentUrl)
// 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
网友评论