文章目录
概要
模块化,就是将代码拆分成独立的块
,各自在代码块中实现各自逻辑
,同时自行决定引入外部代码
,和决定对外暴露一些可以对外提供的特性功能
。
模块化包含下面特征:标识符、依赖、加载、入口等,下面具体说明一下
模块标识符
模块化标识符是所有模块通用的概念
。模块系统可以通过一个标识符
来代表一个模块,然后在需要调用的地方,通过这个标识符来引用对应的模块
。
标识符可以是一个字符串,在原生的实现的模块系统中可能是模块文件的实际路径。有些模块化系统支持明确声明模块的标识。不管哪种方式,完备的模块化系统一定不存在模块标识符冲突问题
模块依赖
模块化系统核心是管理依赖
。指定依赖的模块与周围的环境达成一种契约
。本地模块向模块化系统声明一处外部模块(依赖)
,这些外部模块对于当前模块的运行是必需的
。模块系统会检查这些依赖
,进而保证外部模块能够被正确加载到本地模块
,完成初始化。
每个模块都会有一个唯一的标识符关联,用于检索模块。这个标识符通常是模块本身内部声明的命名空间路径的字符串
或者在某些系统是文件的路径
。
模块加载
加载模块的概念是由模块依赖产生的
。当一个外部模块被指定为依赖的时候,本地模块在准备执行时
,外部模块已经完成初始化
,等着被执行。
在浏览器中,加载模块涉及其中代码执行,但必须在所有依赖都加载并执行之后。
入口
相互依赖的模块必须指定一个入口,这个也是代码执行的起点。因为JavaScript是顺序执行的,并且是单线程,所有代码必须有执行的起点
在ES6之前,出现需要的模块化解决方案,现在具体说明一下
CommonJS
CommonJS 规范,用于Node.js 服务端,实现模块化代码组织,其语法不能直接在浏览器运行。
语法
CommonJS 模块定义需要使用require()
指定依赖,而使用exports 对象
定义自己公共的API
请求模块也会加载相应的模块,而把模块赋值给变量也非常常见
,但赋值不是必须的
,调用require() 意味着模块会原封不动的加载进来
。
下面代码展示
javascript
//moduleA 文件
var moduleB = require("./moduleB");
module.exports = {
stuff: moduleB.doStuff;
}
//moduleC 引入moduleA
var moduleA = require("./moduleA");
moduleA.stuff;
moduleA 通过使用模块定义
的相对路径
来指定自己对moduleB的依赖
在 moudleC 文件,通过require 引入 moduleA ,并给它起一个别名 moduleA
。直接通过moduleA.stuff
便可以访问 moudleA 的staff
.
moudle.exports
对象非常灵活,有多种使用方式。如果只想导出一个实体
,可以直接module.exports
赋值
通过上面的语法,整个模块就导出一个字符串,可以通过下面的方式使用:
单例
模块加载是单例的
,无论在一个模块
在require() 中被引用多少次
,模块只会被加载一次
,模块第一次请求加载后会被缓存,再次请求模块,都只是取得模块的缓存
,如下面的代码所示
AMD
CommonJS 以服务端为目标环境
,能够同步地一次性
把所有依赖都加载到内存
,无需考虑异步加载的问题。
而异步加载模块定义
,AMD( asynchronous Module Definition) 则以浏览器为执行环境
,需要考虑网络延迟问
题,因此实现了按需依次加载依赖
,并在加载完成后,立即执行依赖模块
。
ADM模块实现的核心
是用函数包装模块定义
。这样防止声明全局变量,并允许加载器库控制何时加载
。
包装模块函数是全局的define 的参数
,它是由AMD加载器库的实现定义的
。
语法
AMD 模块可以使用字符串指定自己的依赖,代码示例如下
javascript
/ID为' moduleA'的模块定义。moduleA依赖moduleB
//moduleB会异步加载
define('moduleA',['moduleB'].function(moduleB) [
return (
stuff: moduleB.doStuff();
);
});
AMD 也支持require 和 exports 对象,通过他们可以在AMD模块工厂函数内部定义CommonJS 风格的模块。
动态依赖也是通过这种方式支持的:
UMD
为了统一CommonJs 和 AMD 生态系统,通用模块定义(UMD,universal module definition)规范应运而生。
UMD 创建了两个系统都可以使用的模块代码,本质上UMD会检测依赖使用了哪种系统
,然后进行适配
,并把所有的逻辑包装
在一个立即调用的函数表达式
中。
核心语法
ES6模块化
ES6 引入了模块规范,使得浏览器支持原生的模块化加载。
下面对 ES6模块规范进行说明
模块标签及定义
浏览器中,我们可以通过在 script 标签中 加入 type="module" 的属性,告诉浏览器这一块代码应该作为哦模块执行,而不是作为传统的脚本执行。模块可以嵌入网页中,也可以作为外部文件引入。
javascript
<script type="module">
// 模块代码
</script>
<script type="module src="path/to/myModule,js"></script>
与传统的脚本不同,所有模块都会想 script defer
加载的 脚本一样按顺序执行。 当解析到 script type="moudle"
标签后会立即下载模块文件,但执行会延迟到文档解析完成,因此不会阻塞文档解析
。
下面演示模块执行顺序
模块导出和导入
ES6模块导出和CommonJs非常相似,通过export 关键字导出模块,ES6支持两种导出,命名导出和默认导出,不同的导出方式的引入方式不同。
命名导出和导入
命名导出,可以定义一个变量,然后通过export 将变量导出,例子如下
或者下面的导出也是可以的
导入方式如下:
通过命名导出的,要使用 import 关键字 和{ }
javascript
import {foo} from "./moudleA"
默认导出和导入
默认导出如下:
使用export default 导出foo
javascript
const foo = foo'
export default foo
或者使用下面的方式也是可以的
javascript
const foo = 'foo'
//等同于export default foo:
export {foo as default };
导入方式如下
javascript
import foo from "./foo.js";
//或者使用 as
import {default as foo} from "./foo.js"
如果一个模块有多个命名空间
比如以下
javascript
const foo="foo".bar = "bar",baz = "baz"
export {foo,bar,baz}
我们可以使用 *
一次性全部导入,然后通过as 指定一个别名,通过别名访问对应的命名空间。
javascript
import * as Foo from "./foo.js"
console.log(Foo.foo)//"foo"
命名导出和默认导出混用
如果一个模块使用了命名空间和默认导出
如下面的示例
javascript
export const foo = "foo"
export default const baz ="baz"
导出
javascript
import baz,{foo} from "./foo.js"
模块行为
ES6借鉴了 CommonJS 和AMD 很多优秀的特性,下面简单列举一些
- 模块代码只在加载后执行
- 模块只加载一次
- 模块式单例的
- 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互
- 模块可以请求加载其他模块
- 支持循环依赖
除此之外,ES6还增加自己的特性
- ES6模块在严格模式执行
- ES6不共享命名空间
- 模块顶级this 是undefined
- 模块定义的var 不会添加到window
- ES6模块式异步加载和执行的
小结
- CommonJS 用于Node 服务端模块化,采取module.exports 对象 + require 导出导入
- AMD 异步加载模块化,可以用于浏览器解决脚本异步加载,通过全局的define 函数定义模块
- UMD 实现了AMD 和CommonJs的集成,本质上也是检测并适配。
- ES6 增加了模块化方案,采用export + import 导出导入,还可以支持默认导出