【注】本文为来自CSDN博主「Bal炎魔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接在此
并做了一些细节上的改进~
目录
2. AMD(Asynchronous Module Definition)规范
7、ESModules 在低版本node中使用Babel兼容
模块化的演变
1、文件划分
最早是通过页面的script标签区分,一个标签就是一个模块,通过全局变量来通信;
这种模式的弊端:
- 污染全局作用域,变量可以在外部访问和修改;
- 命名容易冲突;
- 无法管理模块间的依赖关系;
- 全靠相互约定来实现,项目规模变大后,容易出问题;
2、命名空间方式
- 每个模块暴露一个全局对象,模块内的方法通过全局对象传递;
- 只解决了命名冲突问题;
3、函数包裹
- 利用立即执行函数,包裹内容,将其封装在私有空间内,利用全局对象暴露对外通信方法;
- 保证了内部变量的私有性,通过闭包的方式访问;
- 并且利用函数参数,作为模块儿依赖传入,解决模块间依赖的问题;
- 模块儿之间的引用靠人工来写很容易出问题和误操作,遗留隐患
模块化规范
1. CommonJS 规范
node提出的标准,在node运行,以同步模式加载模块,但不适合浏览器中使用,早期未选择。
- 一个文件就是一个模块;
- 每个模块都有单独的作用域;
- 通过module.exports导出成员;
- 通过require函数载入模块
2. AMD(Asynchronous Module Definition)规范
Require.js实现了此规范
大多数第三方库都支持AMD,但是
- 其使用相对较为复杂,容易和业务代码耦合,
- 模块JS文件请求频繁;
3. CMD(Sea.js)
淘宝推出的规范,类似CommonJS规范,但是使用像AMD,为了降低开发者的学习成本;Require.js也兼容了。
4. ES Modules
模块化标准,在node中,就是CommonJS。在浏览器中,就是ES Modules(语言层面统一规范)。
1、基本特性
- 自动采用严格模式,忽略"use strict";
- 每个ESM模块都是单独的私有作用域;
- ESM是通过CORS去请求外部JS模块的;
- ESM的 script 标签自动延迟 执行脚本,即先渲染;
<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
</script>
<script type="module">
console.log(foo) //undefined
</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>
2、导入与导出
导入与导出有几种不同的方式
- 导出
// ------开头用export声明----------
export const name = 'Lili'
export function hello () {
console.log('hello')
}
// ------最后统一用export导出----------
const name = 'Lili'
function hello () {
console.log('hello')
}
export { name, hello }
// -----导出时利用as重命名-----------
export {
name as fooname, // 对应导入时应该用fooname
hello as foohello,
xxx as default // 默认导出
}
// ------设置默认导出---------------
export default name
// 对应导入应该主动重命名
// 或者导入时直接import变量名,去接受这个默认导出的成员(见下)
- 导入
import { name, hello } from './module.js';
// -----------------------------
import xxx, { fooname, foohello } from './module.js';
import { fooname, foohello, xxx as foox} from './module.js'; // 或者这种形式(反正要重命名)
// --导入时直接import变量名-------
import name from './module.js';
- 注意:
1、export { name, hello } 并不是导出一个对象,而是一种固定的写法结构,
因此 import { name, hello } from './module.js';
也不是导出了对对象的解构,也是一种固定的写法结构
不可写为 export { name: age , hello } //报错
而 export default { name: age }是导出一个对象,可以直接写和使用;
2、export 是导出变量的引用关系(无论是否对象),不是复制一份,改变原值这里也是改变;
并且,通过 import 导入的成员是只读的,不可修改;
3、关于导入一些用法
原生的ESModules import导入,后面不能省略,后缀名、不能省略index.js,必须以“./“或者”/“开始,相对和绝对路径,也可以直接使用连接完成的url。
import { name } from './module.js' //不能省略后缀名
import { lowercase } from './utils/index.js' //不能省略index.js
import { name } from '/04-import/module.js' //不能省略“./“(不然会被当成第三方模块)或者”/“(根目录开始)
import { name } from 'http://localhost:3000/04-import/module.js' //可以直接使用完整的url
// --------------
import {} from './module.js'
import './module.js'
// 只加载模块导入模块,不导入具体变量、成员
// ---------------
import * as mod from './module.js'
console.log(mod)
// 导入全部变量,并且作为 mod 内的属性
// ---------------
var modulePath = './module.js'
import(modulePath).then(function (module) {
console.log(module)
})
// 当时导入路径为变量时,可通过全局函数 import 导入,
// 返回一个异步函数 promise,在回调函数中输出导入的值
// ----------------
import { name, age, default as title } from './module.js'
import title, { name, age } from './module.js'
// title为导入的默认值
console.log(name, age, title)
// 同时导入 其他值和默认值时,可以这样写
补充:
// 直接导出 导入的成员,一般可使用在 index.js 中
index.js 作为桥梁,组织起散落在各个模块中的 export,那么别的模块只需import index就行了
同样的,模块中的 default 也需要额外处理,重命名。
export { foo, bar } from './module.js'
export { default as foo1, bar } from './module.js'
// 直接导出 导入的成员,一般可使用在index.js中
4、ESModules 浏览器兼容问题
利用,Polyfill 引入脚本,但是实际工作中不推荐使用,效率较差
<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>
5、node中的ESModules
目前是实验阶段,生成中不推荐使用
node 8.5.0 以上的版本已经支持,使用时要改扩展名为 mjs
并且加”–experimental-modules“ 参数
// node –experimental-modules index.mjs
// 第一,将文件的扩展名由 .js 改为 .mjs;
// 第二,启动时需要额外添加 `--experimental-modules` 参数;
// 我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式(而第三方没有,只提供导出默认成员的方式)
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
// 不支持,因为第三方模块都是导出默认成员
import { camelCase } from 'lodash'
console.log(camelCase('ES Module'))
// 对于第三方的 NPM 模块也可以通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')
6、ESModules 在node中与CommonJS交互
- ESModules中可以导入 CommonJS模块
- 注意 import 不是解构导出对象
- CommonJS始终只会导出一个默认成员
- 而 CommonJS 中不可以导入 ESModulesmok
// CommonJS 模块始终只会导出一个默认成员
// module.exports = {
// foo: 'commonjs exports value'
// }
exports.foo = 'commonjs exports value'
两者的一些差异
CommonJS中的一些全局成员
// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件的绝对路径
console.log(__filename)
// 当前文件所在目录
console.log(__dirname)
其在ESModules中都不能使用,相对应的提供了这些来替换
// 加载模块函数 ESModules
console.log(require) -> import
// 模块对象
console.log(module) -> export
// 导出对象别名
console.log(exports) -> export
// 当前文件的绝对路径
console.log(__filename)-> import.meta.url
// 当前文件所在目录
console.log(__dirname) -> import.meta.url
// ---------------------------------------
// 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)
7、ESModules 在低版本node中使用Babel兼容
安装,@babel/node、@babel/core、@babel/preset-env
.babelrc babel的配置文件
{
“presets”: ["@babel/preset-env"]
}
编译兼容最新特性
{
“plugins”: [
“@babel/plugin-transform-modules-commonjs”
]
}
单独插件的使用