在早期,JS 是没有 模块化
这一概念的,都是通过 script
标签引入 js
文件代码。当然,这对于写一些简单的需求没有什么问题,但当我们的项目越来越庞大时,我们引入的 js
文件就会越多,这时就会出现以下问题:
为了解决以上问题 JavaScript 社区出现了 CommonJS
,CommonJS
是一种模块化的规范,包括现在的NodeJS
里面也采用了部分 CommonJS
语法在里面。那么在后来 ES6
也正式加入了 ES Module
模块。
CommonJS 是一种模块化的规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为 ServerJS,后来为了体现它的广泛性,修改为 CommonJS,平时我们也会简称为 CJS。
Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发。
CommonJS规范中的核心变量:exports、module.exports 和 require。
exports
和 module.exports
负责对模块中的内容进行导出;
require
函数负责帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会被导出。
不能使用:exports = xxx
,在导入后只能拿到一个 {}
。
因为 exports 在默认情况下是指向 module.exports 对象的引用,如果为 exports 赋值了,那么也就是说 exports 不再指向 module.exports 所指的对象的地址,而我们向外共享成员的最终结果是 module.exports 所指的对象,如此便会导致错误。
牢记一条原则:无论是使用 exports 还是 module.exports,最终导出的结果都是以 module.exports 所指向的对象为准。
/** bar.js */
exports.name = "张三"
exports.age = 18
exports.sayHello = function() {
console.log("Hello!")
}
/** main.js */
const bar = require('./bar')
console.log(bar) // { name: '张三', age: 18, sayHello: [Function: sayHello] }
module.exports 和 exports 除了在用法以外,没有区别!
exports 和 module.exports 指向的是同一个对象(为了不混淆,可以理解为 exports 是 module.exports 对象地址的一个引用,exports 本质是一个变量)
/** bar.js */
// 导出一个对象
module.exports = {
name: '张三',
age: 18,
sex: '男',
height: 1.88
}
// 导出任意值
module.exports.name = '张三'
module.exports.age = undefined
module.exports.sex = null
/** main.js */
const bar = require('./bar')
console.log(bar) // { name: '张三', age: undefined, sex: null, height: 1.88 }
CommonJS
支持混合导出,exports
和 module.exports
可以同时使用,但需要注意覆盖问题的发生。
/** bar.js */
exports.name = "张三"
exports.age = 18
module.exports = {
name: '李四',
age: 24,
}
/** main.js */
const bar = require('./bar')
console.log(bar) // { name: '李四', age: 24 }
模块在被第一次引入时,模块中的js代码会被运行一次
模块被多次引入时,会缓存,最终只加载(运行)一次
/** bar.js */
exports.name = '张三'
console.log('bar.js 运行了~')
/** main.js */
require('./bar') // bar.js 运行了~
require('./bar') // 没有输出 因为没有运行
CommonJS
是运行时加载,所以其支持动态导入,也就是支持在js代码中使用 require
语法。
/** bar.js */
exports.name = 'foo'
/** foo.js */
exports.name = 'foo'
/** main.js */
const list = ['./bar.js', './foo.js']
list.forEach(url => require(url)) // 动态导入
require(list[0]) // 动态导入
CommonJs
导出的是值的拷贝,内部对值进行修改不会同步到外部。也可以对拷贝值进行修改。
/** bar.js */
var num = 0
var add = () => ++num
module.exports = { num, add }
/** main.js */
var { num, add } = require('./bar')
console.log(num) // 0
add()
console.log(num) // 0
num = 10
console.log(num) // 10
require()
是同步加载模块。从ES6开始,js原生支持模块化,也就是推出了 ESModule,平时我们也会简称为 ESM。
模块功能主要由两个命令构成:export
和 import
。 export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export
关键字输出该变量。
ESM 语法详细介绍——阮一峰 ES6 入门教程
export
命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的 import
命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。
// 导出变量
export const name = 'bar'
export const p = {
name: '张三',
age: 14,
}
// 导出函数
export function fn() {}
export const test = () => {}
// 批量多个导出
const cat = 'Tom'
const mouse = 'Jerry'
export {
cat,
mouse,
cat as tom, // 使用as关键字重命名
mouse as jerry,
}
// 默认导出,默认导出在同一个模块中只能有一个
export default {
cat,
mouse,
}
/** module.js */
export var name = '张三'
export var age = 18
export default function () {
console.log('module.js')
}
/** main.js */
// 批量导入
import { name, age } from './module'
console.log(name, age) // 张三 18
// 默认导入
import fn from './module'
console.log(fn) // [Function: default]
// 混合导入
import fun, { name as Name, age as Age } from './module'
console.log(fun, Name, Age) // [Function: default] 张三 18
// 模块的整体加载
import * as all from './module'
console.log(all) // [Module: null prototype] { age: 18, default: [Function: default], name: '张三' }
/** module.js */
console.log('module.js 执行了~')
export {
a: 10,
b: 20
}
/** main.js */
import './module' // module.js 执行了~
import './module' // 不会执行,没有输出内容
import { a } from './module'
import { b } from './module'
// 以上两行代码等同于 import { a, b } from './module'
console.log(a, b) // 10 20
因为 export
和 import
需要处于模块顶部,并且是编译时加载,所以 import
命令 无法实现动态导入,但在 ES2020提案 中引入了 import()
函数,支持了动态加载模块。
import()
返回一个 Promise 对象。
if (true) import xxx from 'XXX' // 报错
/** module.js */
export const name = 'module'
/** main.js */
const moduleName = './module.js'
if (true) {
import(moduleName).then(({ name }) => {
console.log(name) // module
})
}
export
导出的是值的引用,并且是只读的,内部对值进行修改会同步到外部。不可以对引用值进行修改。
/** module.js */
var num = 0
var add = () => ++num
export { num, add }
/** main.js */
import { num, add } from './module'
console.log(num) // 0
add()
console.log(num) // 1
num = 30 // 报错
由于 ESModule
模块自动使用严格模式,其 顶层this 执行 undefined。
/** module.js */
console.log(this)
/** main.js */
import './module' // undefined
require()
是同步加载模块,ESM 模块的 import
命令是异步加载,有一个独立的模块依赖的解析阶段;"use strict"
。