AMD,Asynchronous Module Definition, 异步模块定义
。它是一个在浏览器端模块化开发的规范。 它不是javascript
原生支持,因此使用AMD规范进行页面开发须要用到对应的库,也就是RequireJS
,AMD实际上是RequireJS
在推广的过程当中对模块定义的范围化的产出。javascript
requireJS
主要解决两个问题:html
js
文件存在依赖关系时,被依赖的文件须要早于依赖它的文件加载到浏览器js
加载的时候浏览器会阻塞渲染线程,加载文件越多,页面失去响应的时间越长用法: require
须要一个root
来做为搜索依赖的开始(相似package.json
的main
),data-main
来指定这个root
。前端
<script src="script/require.js" data-main="script/app.js"></script>
复制代码
这样就指定了root
是app.js
,只有直接或者间接与app.js
有依赖关系的module
才会被插入到html
中。java
define()
函数:用来定义模块的函数。args0
: 需引入模块的名字数组,arg1
:依赖引入以后的callback
,callback
的参数就是引入的东西。若是有多个依赖,则参数按照引入的顺序依次传入。define(['dependence_name'], (args) => {
// args就是从dependence_name引入的东西
// ... Your fucking code ...
return your_export;
});
复制代码
require()
函数: 用来引入模块的函数。require(['import_module_name'], (args) => {
// args就是从import_module_name引入的东西
// ... Your fucking code ...
});
复制代码
require.config
配置:
baseUrl
:加载module
的根路径paths
:用于映射不存在根路径下面的模块路径shimes
:加载非AMD
规范的js
CMD, Common Module Definition
, 通用模块定义。 CMD
是在sea.js
推广的过程当中产生的。在CMD
规范中,一个模块就是一个文件。node
define(function(require, exprots, module) {
const fs = require('fs'); //接受模块标识做为惟一参数
// exports,module则和CommonJS相似
exports.module = {
props: 'value'
};
});
seajs.use(['test.js'], function(test_exports) {
// ....
});
复制代码
null | AMD | CMD |
---|---|---|
定义module时对依赖的处理 | 推崇依赖前置,在定义的时候就要声明其依赖的模块 | 推崇就近依赖,只有在用到这个module的时候才去require |
加载方式 | async | async |
执行module的方式 | 加载module完成后就会执行该module,全部module都加载执行完成后会进入require的回调函数,执行主逻辑。依赖的执行顺序和书写的顺序不必定一致,谁先下载完谁先执行,可是主逻辑 必定在全部的依赖加载完成后才执行(有点相似Promise.all)。 | 加载完某个依赖后并不执行,只是下载而已。在全部的module加载完成后进入主逻辑,遇到require语句的时候才会执行对应的module。module的执行顺序和书写的顺序是彻底一致的。 |
English time: Common -- 常识 W3C官方定义的API都只能基于Browser,而CommonJS则弥补了javascript这方面的不足。json
NodeJS
是CommonJS
规范的主要实践者。它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global
。 实际用时,使用module.exports
(不推荐使用exports)定义对外输出的API,用require
来引用模块。CommonJS
用同步的方式加载模块。在Server
上模块文件都在本地磁盘,因此读取很是快没什么不妥,可是在Browser
因为网络的缘由,更合理的方案是异步加载。 CommonJS
对模块的定义主要分为:模块引用、模块定义、模块标识3个部分。数组
const fs = require('fs');
复制代码
require的执行步骤:浏览器
js => json => node
(以二进制插件模块的方式去读取)的顺序去识别)require.cache
中,因此屡次require,获得的对象是同一个(引用的同一个对象)(function (exports, require, module, __filename, __dirname) {
// module codes
});
复制代码
exports
、require
方法、module
以及文件定位中获得的完整文件路径
(包括文件名)和文件目录
传递给这个function执行。function fn() {}
exports.propName = fn;
module.exports = fn;
复制代码
一个module
对象表明模块自己,exports
是module
的属性。通常经过在exports
上挂载属性便可定义导出,也能够直接给module.exports
赋值来定义导出(推荐)。缓存
模块标识就是传递给require()
方法的参数,能够是相对路径或者绝对路径,也能够是符合小驼峰命名的字符串。 NodeJS
中CommonJS
的实现:Node
中模块分为Node提供的核心模块
和用户编写的文件模块
。网络
核心模块在Node
源代码的编译过程当中,编译进了二进制执行文件。在Node
启动的时候部分核心模块就加载到了memory
中,因此在引用核心模块的时候,文件定位和编译执行步骤能够省略,而且在路径判断中优先判断,因此它的加载速度是最快的。 文件模块则是在运行时动态加载,须要完整的路径分析,文件定位、编译执行等过程,速度较核心模块慢。 在NodeJS
中引入模块须要经历以下3个步骤:
路径分析:module.paths = [‘当前目录下的node_modules’, ‘父目录下的node_modules’, …, ‘跟目录下的node_modules’]
文件定位:文件扩展名分析、目录和包的处理。
Node
会按.js => .json => .node
的次序补足扩展名依次尝试。(在尝试的过程当中会调用同步的fs模块来查看文件是否存在)Node
会在此目录中查找package.json
,并JSON.parse
出main
(入口文件)对应的文件。若是main
属性错误或者没有package.json
,则将index
做为main
。若是没有定位成功任何文件,则到下一个模块路径重复上述工做,若是整个module.paths
都遍历完都没有找到目标文件,则跑出查找失败错误。编译执行:在Node
中每一个模块文件都是一个对象,编译执行是引入文件模块的最后一个阶段。定位到文件后,Node
会新建一个模块对象,而后根据路径载入并编译。对于不一样的文件扩展名,其载入的方式也有所不一样:
.js
: 经过fs
模块同步读取文件后编译执行.node
:这是C++
编写的扩展文件,经过dlopen()
加载最后编译生成的文件。.json
:同.js
文件,以后用JSON.parse
解析返回结果。 其他文件: 都按js
的方式解析。null | CommonJS | ES6 |
---|---|---|
keywords | exports, require, module, __filename. __dirname | import, export |
导入 | const path = require('fs'); 必须将一个模块导出的全部属性都引入 | import path from 'path'; 能够只引入某个 |
导出 | module.exports = App; | export default App; |
导入的对象 | 随意修改 值的copy | 不能随意修改 值的reference |
导入次数 | 能够任意次require,除了第一次,以后的require都是从require.cache中取得 | 在头部导入,只能导入一次 |
加载 | 运行时加载 | 编译时输出接口 |
ES6的模块已经比较熟悉了,用法很少赘述,直接上码:
import { prop } from 'app'; //从app中导入prop
import { prop as newProp } from 'app'; // 功能和上面同样,不过是将导入的prop重命名为newProp
import App from 'App'; // 导入App的default
import * as App from 'App'; // 导入App的全部属性到App对象中
export const variable = 'value'; // 导出一个名为variable的常量
export {variable as newVar}; // 和import 的重命名相似,将variable做为newVar导出
export default variable = 'value'; // 将variable做为默认导出
export {variable as default}; // 和上面的写法基本同样
export {variable} from 'module'; // 导出module的variable ,该模块中没法访问
export {variable as newVar} from 'module'; // 下面的本身看 不解释了
export {variable as newVar} from 'module';
export * from 'module';
复制代码
ps:ES6模块导入的变量(其实应该叫常量更准确)具备如下特色: 变量提高、至关于被
Object.freeze()
包装过同样、import/export只能在顶级做用域
ES6
模块区别于CommonJS
的运行时加载,import
命令会被JavaScript
引擎静态分析,优先于模块内的其余内容执行(相似于函数声明优先于其余语句那样), 也就是说在文件的任何位置import
引入模块都会被提早到文件顶部。
ES6
的模块 自动开启严格模式,即便没有写'use strict';
。 运行一个包含import
声明的模块时,被引入的模块先导入并加载,而后根据依赖关系,每一个模块的内容会使用深度优先的原则进行遍历。跳过已经执行过的模块,避免依赖循环。
okey~接下来老哥再看看(查查)import
到底干啥了: 标准几乎没有谈到import
该作什么,ES6
将模块的加载细节彻底交给了实现。 大体来讲,js
引擎运行一个模块的时候,其行为大体可概括为如下四步:
js
引擎开始执行加载进来的模块中的代码的时候,import
的处理过程已经完了,因此js
引擎执行到一行import
声明的时候什么也不会干。引入都是静态实现的,等到代码执行的时候就啥都不干了。既然说到了模块(module),那就顺便提一下它和脚本(script)的区别(注意,我这里说的区别仅限于在Web浏览器中):
- | module | script |
---|---|---|
使用方式 (固然还有其余的执行方式,在这里不作过多讨论) | <script src="./source.js type="module" /> | <script src="./source.js type="text/javascript" /> |
下载 | ①遇到<script>时,会自动应用defer。 ②下载 && 解析module。 ③递归下载module中导入的资源。下载阶段完成。 |
遇到<script>时默认阻塞文档渲染,开启下载。 |
执行方式 | ①下载完成后会递归执行module中导入的资源。 ②而后执行module自己。 ps:内联module少了下载module自己的步骤,其余步骤和引入的module相同。 |
默认是下载完成当即执行 |