Appearance
JavaScript模块化
1. 视频地址
本笔记由以下视频整理而来,感谢禹神的分享🌹
2. 模块化概述
2.1 什么是模块化
- 将程序文件依据一定规则拆分成多个文件,这种编程方式就是模块化;
- 拆分出来的每个文件就是一个模块,模块中的数据都是私有的,模块之间互相隔离;
- 同时也能通过一些方法,将模块内的指定数据、方法“交出去”,供其他模块使用;
2.2 为什么需要模块化
随着应用的复杂度越来越高,代码量和文件数量都会急剧增加,会逐渐引发以下问题:
全局污染问题;
如果不同的JavaScript文件中定义了同名的方法或变量,那么会导致先导入的文件被覆盖的问题。


依赖混乱问题;
例如,在如下示例中,JavaScript文件要按照指定的顺序导入,否则会出问题。

数据安全问题;
例如,在以下代码中,我们只想暴露用户的姓名和性别信息,但是如果将整个JavaScript文件导入,则会暴露整个用户信息。


2.3 模块化规范
随着时间的推移,针对JavaScript的不同运行环境,相继出现了多种模块化规范,按时间排序,分别为:
- CommonJS——服务端应用广泛
- AMD——略过
- CMD——略过
- ES6 模块化——浏览器端应用广泛
2.4 导入与导出的概念
模块化的核心思想就是:模块之间是隔离的,通过导出和导入进行数据和功能的共享。
- 导出:模块公开其内部的一部分(如变量、函数等),使这些内容可以被其他模块使用。
- 导入:模块引入和使用其他模块导出的内容,以重用代码和功能。

3. CommonJS 模块化
CommonJS最初名为ServerJS,是于2009年由Kevin Dangoor提出的,用于非浏览器环境的模块化规范,
3.1 CommonJS导出
在CommonJS中,我们可以通过设置module.exports来声明导出,例如在student.js文件中:
js
let name = '张三'
let age = 18
function getTel() {
return '12345678'
}
function getAddress() {
return '北京市'
}
module.exports = {
name,
age,
getTel,
}我们也可以通过如下方式声明导出:
js
exports.name = name
exports.age = age
exports.getTel = getTel但是,我们不能通过以下方式来声明导出:
js
exports = {
name,
age,
getTel,
}原因如下:
模块内部的
this、exports、module.exports在初识时,都指向同一个对象,该空对象就是当前模块导出的数据,如下图:
无论如何修改导出对象,最终导出的都是
module.exports的值。exports是对module.exports的初始饮用,仅为了方便给导出对象添加属性,所以不能使用exports=value的形式导出数据,但是可以使用module.exports=value导出数据。
3.2 CommonJS导入
在CommonJS中,我们可以使用关键字require来导入其他模块,例如,在index.js中:
js
const student = require('./student.js')
console.log(student)输出结果如下:
txt
{ name: '张三', age: 18, getTel: [Function: getTel] }3.3 扩展理解
一个JS模块在执行时,是被包裹在一个内置函数中执行的,所以每个模块都有自己的作用域,我们可以通过如下方式验证这一说法:
js
console.log(arguments)
console.log(arguments.callee.toString())内置函数如下:
js
function (exports, require, module, __filename, __dirname) {
let name = '张三'
let age = 18
function getTel() {
return '12345678'
}
function getAddress() {
return '北京市'
}
module.exports = {
name,
age,
getTel,
}
console.log(arguments)
console.log(arguments.callee.toString())
}可以看到,内置函数的参数中有exports和module,所以我们可以直接使用exports和module。
3.4 在浏览器端使用
环境准备student.js:
js
let name = '张三'
let age = 18
function getTel() {
return '12345678'
}
function getAddress() {
return '北京市'
}
module.exports = {
name,
age,
getTel,
}index.js:
js
const student = require('./student.js')
console.log(student)index.html:
html
<body>
<script src="./index.js"></script>
</body>打开控制台,发现报错:
txt
Uncaught ReferenceError: require is not defined
at index.js:1:17这是由于CommonJS在设计之初,是不支持浏览器环境使用的。如果我们想在浏览器环境使用CommonJS,需要将JS代码编译为浏览器可以识别的,需要使用browserify。
- 安装
browserify:npm i browserify - 使用
browserify编译需要引入浏览器的脚本文件:npx browserify index.js -o build.js - 在网页中引入编译后的文件:
<script src="./build.js"></script>
之后在浏览器的控制台中就能看到结果啦~
4. ES6 模块化规范
ES6模块化规范是一个官方标准的规范,它是在语言标准的层面上实现了模块化功能,是目前最流行的模块化规范,且浏览器与服务端均支持该规范。
4.1 ES6模块化初体验
首先准备student.js:
js
export const name = 'jack'
export const sex = 'male'
function greet() {
console.log('hello');
}
export function sum(num1, num2) {
return num1 + num2
}注意,在name、sex和sum的声明前,我们都加关键字export,表示导出。
然后新建index.js:
js
import * as student from "./student.js";
console.log(student)我们使用import关键词来导入模块。
如果此时我们直接在Node环境中运行index.js,是会报错的:
报错信息
SyntaxError: Cannot use import statement outside a module
所以我们将其引入网页,创建index.html:
html
<script type="module" src="./index.js"></script>注意,我们使用module关键词来标注脚本文件为模块。
之后,在浏览器的控制台中,就能看到引入的内容啦。
如果我们想在Node环境中直接运行index.js文件,需要怎么做呢?其实,从报错信息中我们已经得到了解决方法:
在Node环境中使用ES6模块化
To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
- 方式一:在
package.json文件中加入配置type:"module"; - 方式二:将
js后缀名改为mjs;
在之后的内容中,我们统一在Node环境中进行导出与导入的示范。
4.2 ES6导出
在ES6中,有如下导出方式:
分别导出:在正常声明的前面加上关键字
export,表示导出该声明,例如:jsexport const name = 'jack' export const sex = 'male' function greet() { console.log('hello'); } export function sum(num1, num2) { return num1 + num2 }分别导出结果
[Module: null prototype] {
name: 'jack',
sex: 'male',
sum: [Function: sum]
}
统一导出:在模块声明最后使用关键字
export指定导出的内容,如下:jsconst name = 'jack' const sex = 'male' function greet() { console.log('hello'); } function sum(num1, num2) { return num1 + num2 } export { name, sex, sum }注意,
export后面的内容不是对象,所以{name}不是{name:name}的简写形式,后者会报错。统一导出结果
[Module: null prototype] {
name: 'jack',
sex: 'male',
sum: [Function: sum]
}
默认导出:在模块声明最后使用关键字
export default指定导出的内容,如下:jsconst name = 'jack' const sex = 'male' function greet() { console.log('hello'); } function sum(num1, num2) { return num1 + num2 } export default { name, sex, sum }注意:
export default导出的内容是一个对象,所以{name}不是{name:name}的简写形式。txt默认导出结果 [Module: null prototype] { default: { name: 'jack', sex: 'male', sum: [Function: sum] } }
4.3 ES6导入
对于ES6模块化来说,使用何种导入方式,要根据导出方式决定:
导入全部:可用于所有导出方式
jsimport * as student from './student.js'命名导入:对应于分别导出和统一导出方式
jsimport {name,sex,sum} from './student.js'我们可以使用
as给导入的内容取别名:jsimport {name as studentName,sex,sum} from './student.js'默认导入:对应于默认导出方式
jsimport xxx from './student.js'xxx可以是任意有效标识符,表示默认导出的对象。命名导入和默认导入混合使用
例如,现在有
student.js使用了分别导出、统一导出和默认导出:jsexport const name = 'jack' // 分别导出 const sex = 'male' function greet() { console.log('hello'); } function sum(num1, num2) { return num1 + num2 } export { sex } // 统一导出 export default { sum } // 默认导出然后我们可以使用命名导入和默认导入:
jsimport sumClass, { name as studentName, sex } from './student.js' console.log(sumClass.sum(1, 2)) console.log(studentName) console.log(sex)结果如下:
txt3 jack male动态导入:我们可以在网页中使用
import()动态导入脚本文件,然后执行相应逻辑。首先准备
random.js文件:jsexport function printRandomInternal() { console.log(Math.random()) }然后准备
index.html文件:html<body> <button onclick="printRandom()">动态导入模块,输出随机数</button> <script> function printRandom() { import('./random.js').then(module => { module.printRandomInternal() }) } </script> </body>当我们点击按钮时,会动态导入
random.js文件并执行其中的方法。只导入不接受数据:我们也可以只导入,不接受数据
jsimport "./student.js"使用这种方式,表示在导入时执行
student.js文件中的代码。
4.4 数据引用问题
4.4.1 示例一:普通方法
js
function demo() {
let sum = 1
function increment() {
sum++
}
return {
sum,
increment
}
}
const { sum, increment } = demo()
console.log(sum)
increment()
increment()
console.log(sum);输出结果
1
1
4.4.2 示例二:使用CommonJS
js
let sum = 1
function increment() {
sum++
}
module.exports = { sum, increment }js
const { sum, increment } = require('./02_demo.js')
console.log(sum);
increment()
increment()
console.log(sum);输出结果
1
1
4.4.3 示例三:使用ES6
js
let sum = 1
function increment() {
sum++
}
export {
sum,
increment
}js
import { sum, increment } from "./03_demo.js"
console.log(sum);
increment()
increment()
console.log(sum);输出结果
1
3
4.4.4 结论
ES6模块化规范中,导出和导入的东西使用同一块内存,所以在一个模块中修改同一个数据,会导致该数据在另一个模块中发生变化。
而在CommonJS模块化规范中,数据是使用复制的方式进行导入的,所以在一个模块中修改同一个数据,不会导致该数据在龙一个模块发生变化。
ES6和CommonJS模块化规范中,导入的数据是常量,所以修改导入的数据会报错。