文章目录
CommonJS规范
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过 module.exports 导出成员
- 通过 require 函数载入模块
- CommonJS 是以同步模式加载模块
- node端: node的执行机制是在启动时加载模块。执行过程中不需要加载,只会使用到模块,因此node环境下使用commonjs没有问题
- 浏览器端: 必然导致效率低下,每次页面加载都会导致大量同步模式请求出现,因此早期前端模块化中并没有使用commonjs规范,而是根据浏览器特点设计了专门用于浏览器端的规范AMD(Asynchronous Module Definition),和库Require.js(实现了AMD规范),Require也是个非常强大的模块加载器
- 目前绝大多数第三方库都支持AMD规范,但使用起来相对复杂,另外如果项目模块划分过细,那么同一个页面对js文件的请求次数就会特别多,从而导致页面效率低下
- 同期出现了淘宝Sea.js库+CMD规范,当然后来也被Require.js兼容了
ES Modules 的特性
<!-- 通过给 script 添加 type = module 的属性, 就可以 以 ES Module 的标准执行其中的 JS 代码 -->
<script type="module">
console.log('this is es module')
</script>
相比于普通script标签,esm的特点。自动采用严格模式,忽略 ‘use strict’
<script type="module">
console.log(this); //undefined
</script>
每个 ES Module 都是运行在单独的私有作用域中
<script type="module">
var foo = 100;
console.log(foo);
</script>
<script type="module">
console.log(foo); //报错
</script>
ESM 的script标签会延迟执行脚本 等同于defer属性
默认按执行顺序,script立即执行,页面的渲染会等待脚本执行后再继续渲染
添加type后,执行顺序并不等于引入顺序
<script type="module">
alert("Hello")
</script>
<p>需要显示的内容</p>
ESM 导入和导出
每个模块拥有私有作用域,因此外部无法访问
通过 export, import 进行模块暴露和载入
- export: 模块内对外暴露接口
- import: 导入其他模块提供的接口
// 在两个文件中的基本使用
export var name = 'foo module'
import { name } from './module.js'
console.log(name);
export {
name as fooHellow // as的作用为修改名称
}
export default name
// default 修饰符为默认导出
- export可以导出变量,函数,类
export var foo = {}
export var fn = function(){}
export class Person{}
- 单独使用export,更直观描述导出成员
var foo = {}
var fn = function(){}
export {foo,fn}
- 默认导出写法
export default {foo, fn} //接收对象成员 import mod from '' | mod.fn使用
导出注意事项
export {}不是字面量对象,import引入的也不是解构,都是固定语法,因此 export foo 也是不被允许的,要使用export var foo,而export default {} 默认导出,导出的是字面量对象
# a.js
var foo = {}
var fn = function(){}
export {foo,fn}
...
# b.js
import {foo,fn} from ''
通过export导出的不是值,而是值的地址,外部取值会受到内部值修改的影响
# a.js
export {foo,fn}
setTimeout(function(){foo="ben"},1000)
...
# b.js
import {foo,fn} from ''
console.log(foo)
setTimeout(function(){
console.log(foo)
},1500)
外部导入的成员属于只读成员(常量),无法修改
#b.js
import {foo,fn} from ''
console.log(foo)
foo = "bau" //报错
导入注意事项
- import xx from ‘./module.js’ 路径名称必须完整不能省略,省略会报错
- import xx from ‘/xx/module.js’ 导入内部文件使用相对路径或者绝对路径,’./'不能省略,否则会认为是加载模块
- import xx from ‘http://localhost:3000/xx/module.js’ 可以使用完整的url访问
import {} from ‘./module.js’ 只会执行模块,不会提取成员 - import ‘./module.js’ 上面可以简写成 import ‘模块路径’,导入一些不需要控制的子功能模块非常好用
- import * as mod from ‘./module.js’ 当导出内容很多,用* as mod 提取出来,通过mod.xx使用成员
import() 动态导入模块
导入导出成员
export {name, age} from ‘’ 所有导入成员作为当前模块的导出成员,那么导入的成员无法访问
# index.js 临时文件
import { Button } from './xxx'
import { Avatar } from './xxx1'
export { Button,Avatar }
//改写成
export { Button } from './xxx'
export { Avatar } from './xxx1'
import { Button, Avatar } from 'xxxxxx'
ESM in Node.js
nodejs中使用ESM需要修改文件名从 .js => .mjs
node启动文件使用 node --experimental-modules xxx.mjs
// module.mjs
export const foo = 'hello'
export const bar = 'world'
// index.mjs
import { foo, bar } from './module.mjs'
console.log(foo, bar);
ESM 与 CommonJS交互
总结
- ES Modules 中可以导入 CommonJS 模块
- CommonJS 中不能导入 ES Mdoule 模块
- CommonJS 始终只会导出一个默认成员
- 注意 import 不是解构导出对象
ESM 与 CommonJS差异
运行环境nodemon --experimental-modules xxx.mjs
在commonjs中的对象在ESM中会报错,解决办法是用其他方法代替,报错方法有
ESM in Nodejs 进一步支持
将package.json中添加type = ‘module’,这样不用修改.js为.mjs了
# package.json
{
"type":"module"
}
// 运行命令`nodemon --experimental-modules xxx.js`
如果添加了type的项目中还想使用CommonJS
# common.js
const path = require('path')
console.log(path.join(__dirname,'foo'))
// 会报错,解决办法将commonjs文件修改为 common.cjs
ESM in Nodejs Babel兼容方案
早期node版本8.0.0,通过babel来让node运行esm
安装babel-mode yarn add @babel/node @babel/core @babel/preset-env --dev
babel是基于插件机制去实现的,核心core和preset-env并不会转换代码,具体转换特性通过插件
一个插件转化一个特性,preset-env是插件集合,这个集合包含了js标准中所有新特性
实际帮我们转换的是集合里的插件
可以通过yarn babel-node index.js --presets=@babel/preset-env
或者放入配置文件.babelrc json格式文件
{
"presets":["@babel/preset-env"]
}
执行yarn babel-node index.js
或者移除集合使用插件
通过 yarn remove @babel/preset-env
yarn add @babel/plugin-transform-modules-commonjs --dev
{
"plugins":[
"@babel/plugin-tranform-modules-commonjs"
]
}