前端模块化规范是一个老生常谈的话题,今天我们了解一下 CommonJS
模块化规范。
CommonJS
出现的原因
在古代前端开发中,通过script
标签管理前端代码模块。但script
的问题也显而易见
- 脚本多变,需要手动管理加载顺序
- 模块之间的依赖关系难以管理
- 不同脚本之间逻辑调用,需要通过全局变量的方式
- 容易造成全局变量污染
- 容易产生命名冲突
- 只能在
html
中使用
所谓乱世出英雄,CommonJS
规范应运而生。该规范约定,一个文件就是一个模块,每个模块都有单独的作用域。通过 exports
或 mdoule.exports
导出模块,通过 require
引入模块。
CommonJS
以同步的方式加载模块
浏览器端使用 即时编译 的方式执行代码,即:代码的每一行都在运行时编译和执行。
如果在浏览器端使用同步的方式加载模块,就会引起大量的同步请求,导致应用运行效率低下。所以 浏览器端不适合使用CommonJS
服务器端使用 提前编译 的方式执行代码,即:在编译阶段,在程序执行之前,源代码已被转换为机器代码。
Node.js 的模块加载机制也是类似的范式:在启动时加载模块,执行过程中只是使用模块。所以,Node.js 适合使用CommonJS
因此Node.js 系列的应用一般都遵循CommonJS
模块化规范,如
- Webpack
- Gulp
- Eletron
- .......
exports
exports
导出的是一个对象,该对象中可以包含任何数据类型,如
- 字符串
- 函数
- 对象
- ......
代码示例📄
// src/lib.js
exports.hello = 'hello'
exports.add = function (a, b) {
return a + b
}
exports.obj = { name: 'a', age: 20 }
setTimeout(() => {
/* 打印出
{
hello: 'hello',
add: [Function],
obj: { name: 'a', age: 20 },
className: '1年级'
}
*/
console.log(exports);
}, 3000);
// src/index.js
const lib = require('./lib.js')
// 打印出 : { hello: 'hello', add: [Function], obj: { name: 'a', age: 20 } }
console.log(lib);
lib.className = '1年级'
module.exports = 'a'
exports
导出的对象与外部变量lib
是同一个引用。因为外部变量lib
可以修改lib.js
文件的内容
通过 webpack打包代码分析 exports
手写-模拟打包后的exports代码
其实 module
和 exports
只是两个单纯的变量,为了更方便理解, 把exports
替换为变量 e,
const webpack_modules = {
// 如果通过 module.exports 导出, 则有三个参数,并直接给 module赋值
"./src/index.js": ((m, __unused_webpack_exports, __webpack_require__) => {
const lib = __webpack_require__(/*! ./lib.js */ "./src/lib.js")
/* 打印出
{
hello: 'hello',
add: [Function],
obj: { name: 'a', age: 20 },
className: '1年级'
}
*/
console.log(lib);
console.log('====================================');
lib.className = '1年级'
m.e = 'a'
}),
// 如果通过 exports 导出, 则有两个个参数,并直接给 module.e赋值
'./src/lib.js': ((__unused_webpack_module, e) => {
e.hello = 'hello'
e.add = function (a, b) {
return a + b
}
e.obj = { name: 'a', age: 20 }
setTimeout(() => {
/* 打印出
{
hello: 'hello',
add: [Function],
obj: { name: 'a', age: 20 },
className: '1年级'
}
*/
console.log(e);
console.log('====================================');
}, 3000);
})
}
const webpack_module_cache = {};
function webpack_require(moduleId) {
// 加载缓存
if (webpack_module_cache[moduleId]) {
return webpack_module_cache[moduleId].e;
}
// 创建 moudle.e 对象
var module = webpack_module_cache[moduleId] = {
e: {}
};
// 执行此函数为 module或moudle.e赋值
webpack_modules[moduleId](module, module.e, webpack_require);
// 赋值结束,返回module.e
return module.e;
}
webpack_require("./src/index.js");
mdoule.exports,exports同时存在
// src/lib.js
exports.hello = 'hello'
exports.add = function (a, b) {
return a + b
}
exports.obj = { name: 'a', age: 20 }
setTimeout(() => {
console.log('============打印出 { hello: 'hello', add: [Function], obj: { name: 'a', age: 20 } }========================');
console.log(exports);
console.log('====================================');
}, 3000);
module.exports = function min(a, b) {
return a - b
}
// src/index.js
const lib = require('./lib.js')
console.log('==========打印出 [Function: min]==========================');
console.log(lib);
console.log('====================================');
lib.className = '1年级'
module.exports = 'a'
mdoule.exports
,exports
同时存在, mdoule.exports
会覆盖 exports
, 原理结合上图 webpack打包后的代码 和 手写-模拟打包后的exports代码 就可以理解。
因此 在真实场景中,推荐使用 mdoule.exports
以上就是 CommonJS
的一点个人浅解,有不足的地方多谢各位看官补充😁