本文介绍了常用的 JavaScript 模块化规范的使用说明,包含 ESMA6、CommonJS 等其它规范。
视频链接
ES6 模块规范
ES6 模块规范主要用于客户端,也可用于服务端。
使用方式
在 node.js 服务端有两种使用方式:
- 将
.js
文件改为 .mjs
文件。
- 或者在
package.json
中添加定义 'type': 'module'
。
在浏览器端使用:
- 在 html 文件中使用
<script type="module" src="index.js"></script>
引入。
直接在本地使用浏览器打开 html 文件浏览器会报错,请启动 web 服务器后通过 IP:端口
访问网页。
导出数据
导出有三种方式:
1 2 3 4 5 6 7 8 9
| export const name = 'zhangsan'; export const age = '18'; export function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
|
1 2 3 4 5 6 7 8 9 10 11
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
export { name, age, getTel };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
export default { name, age, getTel };
|
三种导出可以混用:
1 2 3 4 5 6 7 8 9 10 11 12
| export const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
export { age }; export default { getTel };
|
会得到:
1 2 3 4 5
| [Module: null prototype] { age: '18', default: { getTel: [Function: getTel] }, name: 'zhangsan' }
|
导入数据
对于 ES6 模块化来说,使用何种导入方式要根据导出方式决定。
1 2
| import * as student from './student.js';
|
1 2
| import { name as newName, age, getTel } from './student.js';
|
1 2 3
| import student from './student.js';
|
默认导入和命名导入可以混用(当然不能和全部导入混用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
export { age }; export default { getTel };
import newFun, { name, age } frome './student.js';
|
1 2 3 4 5 6 7
| console.log('hello world!');
import './log.js';
|
导入还可以根据事件来进行动态导入,下面的例子将演示当点击按钮时才导入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
export { name, age, getTel };
const btn = document.getElementById('btn'); btn.onclick = async() => { const result = await import('./student.js'); console.log(result); }
|
CommonJS 规范
CommonJS 规范主要用于服务端,如果想要在浏览器端使用,可以通过 browserify 将原本的 js 文件进行翻译,再将翻译后的文件在 html 中引用。
导出数据
在 CommonJS 规范中有两种导出方式,导出的是一个普通对象,默认为 {}
。
通过 exports
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
exports.name = name; exports.age = age; exports.getTel = getTel;
|
通过 module.exports
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
module.exports = { name, age, getTel };
|
导入数据
导入时会执行被导入文件中的代码:
1 2 3 4 5 6 7 8 9 10
| console.log('hello');
const student = require('./student.js'); console.log(student);
|
导入时也能通过解构赋值:
1 2
| const { name: newName, age, getTel } = require('./student.js');
|
注意
注意点如下:
- 每个模块内部的:
this
、exports
、module.exports
在初始时都指向同一个空对象。console.log(this===exports && exports === module.exports); // true
。
- 无论如何修改导出对象,最终导出的都是
module.exports
的值。
exports
是对 module.exports
的初始引用,仅为了方便给导出对象添加属性,所以不能使用 exports = value
的形式导出数据,可以使用 exports.value = value
、module.exports = value
导出数据。
思考以下代码在导入时获取到的数据是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
exports = { a: 1 }; exports.b = 2; module.exports.c = 3; module.exports = { d: 4 };
|
1 2 3 4
| const student = require('./student.js'); console.log(student);
|
分析:
exports
-> {}
<- module.exports
exports
-> { a: 1 }
;module.exports
-> {}
exports
-> { a: 1, b: 2 }
;module.exports
-> {}
exports
-> { a: 1, b: 2 }
;module.exports
-> { c: 3 }
exports
-> { a: 1, b: 2 }
;没有东西指向 { c: 3 }
;module.exports
-> { d: 4 }
模块化的好处
模块化能够有效解决:
1、全局污染、数据安全问题
在使用模块化以前:
1 2 3 4 5 6 7 8 9
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="text/javascript" src="student.js"></script> <title>Document</title> </head> <body> </body> </html>
|
此时在浏览器控制台是能够获取到 name
、age
等变量的,并且如果引入的多个文件有相同的变量名会产生冲突。
使用模块化以后:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const name = 'zhangsan'; const age = '18'; function getTel (){ return '13333333333'; } function getSubject (){ return ['Java', 'C++', '高等数学']; } export default { name, age, getTel };
import student from './student.js';
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="module" src="index.js"></script> <title>Document</title> </head> <body> </body> </html>
|
此时在浏览器控制台是无法获取到 student.name
变量的,而且在导入模块时能够对冲突的变量重命名,解决了数据安全、全局污染问题。
2、依赖混乱问题
在使用模块化以前,直接在 html 文件中引入 js 的顺序至关重要,尤其是需要引入多个 js 文件时,特别需要关注他们的依赖关系来确定引入顺序,十分麻烦。使用模块化后,可以在各个 js 文件中显式导入需要使用到的模块,而不用关心 html 文件中引入 js 的顺序。
数据引入问题
思考同一代码在 CommonJS 规范和 ES6 规范的执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| let sum = 1; function increment() { sum += 1; } module.exports = { sum, increment };
const { sum, increment } = require('./data.js'); console.log(sum); increment(); increment(); console.log(sum);
|
输出:
1
data: 2
data: 3
1
1 2 3 4 5 6 7 8 9 10 11 12 13
| let sum = 1; function increment() { sum += 1; } export { sum, increment };
import { sum, increment } from './data.js'; console.log(sum); increment(); increment(); console.log(sum);
|
输出:
1
data: 2
data: 3
3
对比两者发现,ES6 规范中执行完函数后的 sum
的值不一样了,是因为 index.js
和 data.js
中的 sum
是同一片内存空间(在 CommonJS 规范中导入是将值进行了复制),为了避免这种情况应该在 data.js
中声明常量而不是变量,即改为 const sum = 1
。
其它规范
AMD 规范 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 规范 是 SeaJS 在推广过程中对模块定义的规范化产出。