前言
读书笔记以及额外的补充
模块模式
逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。
模块系统的本质是键值对的实体,每一个模块都有可用于引用它的标示符。这个标示符,可能是字符串或者实际的路径。将模块标示符,转化为真正的模块, 不同的模块系统有不同的实现。
模块加载
加载模块其中的代码,必须在所有依赖加载完成后。如果浏览器没有收到依赖模块的代码,则必须发送请求并等待网络返回。收到模块代码之 19 后,浏览器必须确定刚收到的模块是否也有依赖。然后递归地评估并加载所有依赖,直到所有依赖模块 都加载完成。只有整个依赖图都加载完成,才可以执行入口模块。
入口
入口模块的加载,必须等待依赖加载完成后,才能执行。
异步依赖
模块在必要时加载,并提供回调,
动态依赖
动态依赖可以支持更复杂的依赖关系,但代价是增加了对模块进行静态分析的难度。
if (loadCondition) {
require('./moduleA');
}
静态分析
对静态分析友好的模块系统可以让模块打包系统更容易将代码处理 为较少的文件。对于动态依赖,静态分析将会变得更加困难。
循环依赖
CommonJS,AMD,ES6模块都支持循环依赖,加载器会执行深度优先的依赖加载。
凑合的模块系统
在ES6模块之前,使用IIFE(立即调用函数表达式)将模块封装在匿名闭包之中。
// FOO模块
const Foo = (function() {
return {
bar: 'baz',
baz: function() {
return this.bar
};
})();
Foo.bar
Foo.baz()
// 泄漏模块模式
const Foo = (function() {
const bar = 'baz';
const baz = function() {
console.log(bar);
};
return {
bar: bar,
baz: baz
};
})();
Foo.bar
Foo.baz()
IIFE模块也可以接受外部传参
const params = 100;
const Foo = (function(params) {
const bar = params;
const baz = function() {
console.log(bar);
};
return {
bar: bar,
baz: baz
};
})(params);
CommonJS模块
Nodejs采用的是经过修改的CommonJS规范
CommonJS模块,使用require指定依赖,使用exports对象定义模块的API。模块赋值给变量是常见的形式。但不是必须的。在一个模块中,引入另外一个模块多次,都只会加载一次,模块是单例模式。模块在加载过一次后,会被缓存(如果是第一次加载,会加载模块,如果不是第一次加载改模块,会直接使用模块的引用)。
const moduleB = require('./moduleB');
module.exports = {
stuff: moduleB.doStuff();
};
在CommonJS中模块加载是同步的操作,CommonJS支持动态依赖
if (loadCondition) {
require('./moduleA');
}
CommonJS模块如果想在浏览器中使用,需要使用到打包工具。打包工具通常会将模块的代码,打包到函数闭包中(还是依赖IFEE实现的😂)。
AMD模块
AMD模块使用函数包装模块定义,模块代码放在函数里,避免了全局污染,可以防止全局变量的出现,方便加载器库控制何时加载模块,包装模块的函数是define,define函数是由AMD自己实现的,AMD会在依赖模块异步加载完成后,立即调用模块的工厂函数
// 模块名为moduleA,依赖了moduleB,导出tuff
// moduleB是异步加载的
define('moduleA', ['moduleB'], function(moduleB) {
return {
tuff: moduleB.doStuff();
};
});
AMD模块内部同样支持require,exports关键字, 实现CommonJS风格的模块。
define('moduleA', ['require', 'exports'], function(require, exports) {
var moduleB = require('moduleB');
exports.stuff = moduleB.doStuff();
});
define('moduleA', ['require'], function(require) {
if (condition) {
var moduleB = require('moduleB');
}
});
UMD模块
UMD模块是一种通用的模式,用于兼容AMD和CommonJS的规范。UMD规范同时兼容amd和commonjs,并支持传统的全局变量的模式。
// UMD示例,不过这些样板代码,应该由构建工具自动生成
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('jquery'));
} else {
// 全局变量模式
// root是window对象
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// factory 函数,用于消费模块
}));
ES6模块
带有type="module"的script标签,会告诉浏览器应作为模块执行。当浏览器解析到
<!-- 第二个执行 -->
<script type="module"></script>
<!-- 第三个执行 -->
<script type="module"></script>
<!-- 第一个执行 -->
<script></script>
与
模块加载
完全支持 ECMAScript 6 模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器 会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它 们的内容,确定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这个异步递归加载过 程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。
模块行为
- 模块代码只在加载后执行。
- 模块只能加载一次。
- 模块是单例。
- 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。
- 模块可以请求加载其他模块。
- 支持循环依赖。
- ES6模块默认在严格模式下执行。
- ES6模块不共享全局命名空间。
- 模块顶级 this 的值是 undefined(常规脚本中是 window)。 模块中的 var 声明不会添加到 window 对象。
- ES6模块是异步加载和执行的。
模块导出
导出分为默认导出和命名导出,export只允许在顶级块中使用,export的位置对模块没有影响,export可以出现在导出值的之前。
// 命名导出
// 行内命名导出
export const foo = 'foo';
// 命名导出
const foo = 'foo';
export { foo };
// 命名导出的别名
const foo = 'foo';
export { foo as myFoo };
// 一个模块可以有多个命名导出
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
export { foo, bar as myBar, baz };
// 或者这样做
export const foo = 'foo';
export const bar = 'bar';
export const baz = 'baz';
// 默认导出
// 默认导出使用default关键字
// 一个模块内,可以同时包含命名导出,和默认导出
const foo = 'foo';
export default foo;
// 命名导出如果设置别名为default等同于默认导出
// 下面的两者等同
export { foo as default };
export default foo;
// 既实现了默认导出也实现命名导出
export { foo as default, foo };
模块导入
import只能出现在模块的顶级,import会被自动提升到模块的顶级,所以import在模块中的位置并不重要。导入的内容对于模块是只读的。
默认导出与命名导出的区别主要体现在导入上的不同
// 对于默认导出
import { default as foo } from './foo.js';
import foo from './foo.js';
// 对于命名导出
import { foo, bar, baz as myBaz } from './foo.js';
import * as Foo from './foo.js';
模块转移导出
这快阮老师的书讲解的更细致
export * from './foo.js';
// 等同于
import { a, b } from './foo.js';
export a;
export b;
// 或者直接确认要导出的值
export { foo, bar as myBar } from './foo.js';
对于默认导出,可以这样做
// 对于默认模块
export { default } from './foo.js';
// 等同于
import Foo from './foo.js';
export default Foo;
也可以将命名导出,修改为默认导出
export { foo as default } from './foo.js';
或者将默认导出修改为命名导出
export { default as foo } from './foo.js'
工作者模块
Worker可以接受模块文件,但是需要在第二个参数中说明
const scriptWorker = new Worker('scriptWorker.js');
const moduleWorker = new Worker('moduleWorker.js', { type: 'module' });
向后兼容
推荐看一下这两篇文章,
这是向后兼容的示例,经过我的测试,包的大小缩小了一半。