一、模块化概述
1.1 为什么js代码需要模块化?
<script>
let NAME_1 = 'name1';
let NAME_2 = 'name2';
let NAME_3 = 'name3';
function factory1() {
return { name: NAME_1 }
}
function factory2() {
return { name: NAME_2 }
}
function factory3() {
return {
o1: factory1(),
o2: factory2(),
name: NAME_3
}
}
</script>
当我们的代码复杂度变高时,如果还是如上述代码这样将所有的功能定义在全局
1.那全局作用域容易被污染 2.同时代码会非常难以维护
这个时候我们尝试将代码拆分成不同的文件,同一个功能下的代码单独维护,如下:
<script src="./1.js"></script>
<script src="./2.js"></script>
<script src="./3.js"></script>
对于问题1,这样的做法不能解决,全局污染的问题仍旧存在。 对于问题2,能一定程度上改善,增加代码的可维护性。
同时会产生新的问题:因为不同文件的js代码之间存在依赖关系,所以需要注意js的加载顺序。
典型的例子就是当我们的代码使用jquery时,我们需要在引入我们的代码前引入jquery
<script src="jquery.js"></script>
<script src="mycode.js"></script>
1.2 如何实现模块化?
var m1 = (function m1() {
let name = 'name1'
module.factory = function () {
return { name }
}
return module
})()
var m2 = (function m2() {
let name = 'name2'
module.factory = function () {
return { name }
}
return module
})()
通常,我们可以使用闭包和命名空间的方式实现一个模块
1.3 有哪些模块化方案?
- commonjs: nodejs实现的模块规范,用于服务端
- es6 Module: es6提出的模块规范,是一种静态的模块依赖描述,用于浏览器端/服务端
- amd/cmd: require.js实现的模块规范,基本只用于浏览器端
以下是这几种规范简单差异:
| 标题 | 提出者 | 平台 | 动态/静态 | 同步/异步 |
|---|---|---|---|---|
| commonjs | nodejs | 服务端 | 完全runtime的,动态的依赖关系 | 同步 |
| es6 module | es6 | 浏览器/服务端 | 静态,需要在编译阶段确定依赖关系 | 通常是异步 |
| amd | require.js | 浏览器端 | 静态,需要编译 | 异步 |
二、模块化规范
2.1 amd - 异步模块加载规范
require.js提出的一种规范,通过define函数定义一个模块,通过require函数引入一个模块,并以异步回调的方式执行后续的代码。
defined('module', function())
require(['module'], function(module) {
console.log(module.name);
module.sayHello();
});
2.2 commonjs和es6 module的区别
commonjs是nodejs提出的模块加载规范,实现了服务端js的模块化;es6 module是es6提出的模块规范。
两者主要有以下区别:
2.2.1 commonjs是runtime的,es6是静态依赖关系
同时require一定是同步的,而es6 module的import在编译后往往是异步的
const module = require('module.js');
编译的时候不会对依赖关系做处理,代码运行时,读取module.js文件,在内存中运行并获取一个导出的值(可以是数字,对象,函数)。
import module from './module.js';
编译阶段会做建立静态的依赖关系,当运行代码时,会将module.js的代码提前定义执行到内存中,module只是一个引用。
// 所有模块的代码都存入__webpack_modules__ ,以唯一的id作为索引
var __webpack_modules__ = ([])
// 编译后的代码使用__webpack_modules__ 方法 + id访问模块
// 例如 import { module } from './module.js' -> module = __webpack_require__(1);
var __webpack_require__(id) = ...
2.2.2 commonjs的require读取返回一个值,es6 import是获取一个值的引用。
当你对引用模块的内部变量做修改时,其他引用该模块的地方也会受影响。使用require引入一个模块,会在运行阶段读取并记加载一个值到内存中,对同一个模块可以多次加载。
require函数
// 这里两次require后得到相同的引用,是因为require本身有缓存机制
const module1 = require('./1.js');
const module2 = require('./1.js');
console.log(module1 === module2) // true
// 手动清除缓存后,require读取两次文件,执行两次,导出2个结果值到内存中
const module1 = require('./1.js');
delete require.cache[require.resolve('./1.js')];
const module2 = require('./1.js');
console.log(module1 === module2) // false
使用es6 import引入一个模块,引入的地方会获得一个引用,指向这个模块对应的值,且是全局唯一的。
import { module } from './module.js';
// 其他地方也会受影响, ./module.js这个模块只有一个唯一的id,指向是唯一的
module.value = 1
2.2.3 commonjs不要求在编译阶段确定模块路径,es6模块需要在编译阶段确定模块路径
// path是动态的,只有在runtime阶段才会确定
// require可以正常运行
const path = (await getConfig()).modulePath;
require(path)
// path是动态的,只有在runtime阶段才会确定
// es6 import通常会在编译阶段报错,因为无法找到对应的文件打包到结果中
const path = (await getConfig()).modulePath;
import(path)
三、总结
| 规范 | 提出者 | 平台 | 静态or动态 | 同步or异步 | 返回 |
|---|---|---|---|---|---|
| amd | require.js | 浏览器端 | runtime | 异步的 | 加载后的返回值 |
| common.js | nodejs | 服务端 | runtime | 同步的 | 加载后的返回值 |
| es6 | es6规范 | 浏览器端 & 服务端 | 静态依赖关系,依赖编译 | 通常是异步的 | 模块的引用 |