Script脚本
还早在前端即jquery时代,模块加载都用的是script标签,是最古老的一种模块加载方式。
它的不足:
- 全局变量:存在命名冲突污染隐患
- 依赖管理:各脚本的编写和加载有先后要求
- 串行执行:加载的时候,浏览器会停止网页渲染,加载的文件越多、越大,页面失去响应的时间越长
CommonJS
是由Mozilla工程师于2009年开始的一个项目,目的是为了让除浏览器之外的客户端(如:服务器端或桌面端)对javascript
进行模块化开发。
主要适用于服务器端开发,如Node.js,采用同步加载模块策略。
// 关键字
module.exports = {}
require('')
路径加载方式
require('/')
如果以/开头,则是按绝对路径加载;
以./或../,则是相对路径;
不以以上方式加载,如果是非核心模块(node是核心模块优先),则会加载node_modules下模块,如:require('path')
exports对象会被引用方修改吗
思考如下代码:
// lib.js
exports.foo = {
name: 'lib'
}
setTimeout(() => {
console.log(exports) // { foo: { name: 'lib' }, modify: true }
}, 1000)
// index.js下引入lib.js
const lib = require('./lib.js')
lib.modify = true
从打印出的日志可以看出:在引用方修改导出的lib对象,exports对象是被修改的。说明了两个对象指向的是同一块内存地址。
这样会不会有问题,既然我们需要模块化,而外部可以随便修改它。
module.exports
修改上述代码:
// lib.js
exports.foo = {
name: 'lib'
}
// 新增一个add导出函数
module.exports = function add(a, b) {
return a + b
}
setTimeout(() => {
console.log(exports) // { foo: { name: 'lib' } }
console.log(module.exports) // [Function: add] { modify: true }
}, 1000)
// index.js下引入lib.js
const lib = require('./lib.js')
lib.modify = true
console.log(lib) // [Function: add] { modify: true }
在lib.js新增一个module.exports
导出函数add,打印出的结果不同,导出的对象变成了module.exports
。
我们再略作修改:
module.exports = function add(a, b) {
return a + b
}
// 改成
module.exports.add = function(a, b) {
return a + b
}
// 三处打印结果相同
// { foo: { name: 'lib' }, add: [Function (anonymous)], modify: true }
这样说明都指向的是同一个对象。
为什么会这样呢?
我们来看看通过webpack5编译之后的代码:
(() => {
var __webpack_modules__ = ({
"./index.js":((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
eval("const lib = __webpack_require__(/*! ./module/lib.js */ \"./module/lib.js\")\r\nlib.modify = true\r\nconsole.log(lib, 'lib index...')\n\n//# sourceURL=webpack://node/./index.js?");
}),
"./module/lib.js":((module, exports) => {
eval("exports.foo = {\r\n name: 'lib'\r\n}\r\n\r\nmodule.exports = function add(a, b) {\r\n return a + b\r\n}\r\n\r\nsetTimeout(() => {\r\n console.log(module.exports, 'lib2...')\r\n console.log(exports, 'lib...')\r\n}, 1000)\r\n\n\n//# sourceURL=webpack://node/./module/lib.js?");
})
});
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
var __webpack_exports__ = __webpack_require__("./index.js");
})();
注释都去掉了,代码不多。核心的逻辑在于eval函数执行模块本身的代码:
// 第1个入参是导出的module,第2个入参是module.exports的引用
__webpack_modules__[moduleId](module, module.exports, __webpack_require__)
如果lib.js
中仅是通过exports的引用来新增参数,那返回的内存地址并没有变化,仅新增了一些值;但如果模块中存在module.exports的赋值,例如函数,则会改变整个module的内存地址。
再简化一下这个函数:
const moduleX = {
exports: {}
}
function test (moduleX, exports) {
// exports仅是一个引用地址,若没有函数赋值来改变moduleX.exports的地址,则返回的将是{foo: 123}
moduleX.exports = function f1 () {}
exports.foo = 123
return moduleX.exports
}
console.log(test(moduleX, moduleX.exports)) // [Function: f1]
AMD
全称:Asynchronous Module Definition(异步模块定义)。是从CommonJS讨论中诞生的,优先照顾了浏览器的模块加载场景,使用了异步加载和回调。
// lib/sayModule.js
define(function (){
return {
sayHello: function () {
console.log('hello');
}
};
});
define(['./lib/sayModule'], function (say){
say.sayHello();
})
RequireJS
是前端模块化管理工具,遵循AMD规范,通过一个函数来将所需要的或者说所依赖的模块装载进来,然后返回一个新的函数或模块,所有的有关新模块的操作都在内部操作。
CMD
全称:Common Module Definition(公共模块定义)。在CMD中,一个模块就是一个文件。
// 定义JSON
define({"foo": "100"})
// 定义函数时,表示模块的构造方法,执行构造方法便可以得到模块向外提供的接口
define(function(require, exports, module) {
// 模块代码
})
AMD和CMD的区别在于:
对依赖模块的执行时机不同,而不是加载模块的时机不同。两者加载模块都是异步的,只不过AMD在主逻辑执行前就已经知道依赖的模块是什么,而CMD是在执行主逻辑代码的时候才知道依赖于谁。
UMD
全称:Universal Module Definition(统一模块定义)。是将AMD和CommonJS合并在一起的一种尝试。
(function(define) {
define(function () {
return {
sayHello: function () {
console.log('hello');
}
};
});
}(
typeof module === 'object' && module.exports && typeof define !== 'function' ?
function (factory) { module.exports = factory(); } :
define
));
该模式的核心思想是IIFE,会根据环境来判断需要的参数类别。运行时动态编译。
ES6 Module
自动采用严格模式,不管在模块头部是不是加了"use strict"。运行前编译。
a.js
export const a = 1;
// 引入
import a from 'a.js'