web系列之模块化——AMD、CMD、CommonJS、ES6 整理&&比较
栏目: JavaScript · 发布时间: 7年前
内容简介:用法:
AMD,Asynchronous Module Definition, 异步模块定义
。它是一个在浏览器端模块化开发的规范。
它不是 javascript
原生支持,所以使用AMD规范进行页面开发需要用到对应的库,也就是 RequireJS
,AMD其实是 RequireJS
在推广的过程中对模块定义的范围化的产出。
requireJS
主要解决两个问题:
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
中。
-
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
CMD, Common Module Definition
, 通用模块定义。 CMD
是在 sea.js
推广的过程中产生的。在`CMD规范中,一个模块就是一个文件。
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的执行顺序和书写的顺序是完全一致的。 |
三、CommonJS
English time: Common -- 常识 W3C官方定义的API都只能基于Browser,而CommonJS则弥补了javascript这方面的不足。
NodeJS
是 CommonJS
规范的主要实践者。它有四个重要的环境变量为模块化的实现提供支持: module、exports、require、global
。
实际用时,使用 module.exports
(不推荐使用exports)定义对外输出的API,用 require
来引用模块。 CommonJS
用同步的方式加载模块。在 Server
上模块文件都在本地磁盘,所以读取非常快没什么不妥,但是在 Browser
由于网络的原因,更合理的方案是异步加载。 CommonJS
对模块的定义主要分为: 模块引用、模块定义、模块标识
3个部分。
1、模块引用:
const fs = require('fs');
复制代码
require的执行步骤:
- 如果是核心模块, 如fs,则直接返回模块
- 如果是路径,则拼接成一个绝对路径,然后先读取缓存require.cache再读取文件。(如果没有扩展名,则以js => json => node(以二进制插件模块的方式去读取)的顺序去识别)
- 首次加载后的模块会在require.cache中,所以多次require,得到的对象是同一个(引用的同一个对象)
- 在执行模块代码的时候,会将模块包装成一下模式,以便于作用域在模块范围之内。
(function (exports, require, module, __filename, __dirname) {
// module codes
});
复制代码
-
包装之后的代码同过vm原生模块的runInThisContext()方法执行(类似eval,不过具有明确上下文不会污染环境),返回一个function对象。
最后将当前模块对象的
exports、require方法、module以及文件定位中得到的完整文件路径(包括文件名)和文件目录传递给这个function执行。
2、模块定义:
function fn() {}
exports.propName = fn;
module.exports = fn;
复制代码
一个 module
对象代表模块本身, exports
是 module
的属性。一般通过在 exports
上挂载属性即可定义导出,也可以直接给 module.exports
赋值来定义导出(推荐)。
3、模块标识:
模块标识就是传递给 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模块
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
引擎运行一个模块的时候,其行为大致可归纳为以下四步:
- 解析:engine去解析模块的代码,检查语法等。
- 加载:递归加载所有被引入的模块, 深度优先 。
-
链接:为每个新加载的模块创建一个作用域,并将模块中的声明绑入其中(包括从其他模块中引入的)。
当
js引擎开始执行加载进来的模块中的代码的时候,import的处理过程已经完了,所以js引擎执行到一行import声明的时候什么也不会干。引入都是静态实现的,等到代码执行的时候就啥都不干了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android模块化改造以及模块化通信框架
- Laravel 模块化开发模块 – Caffienate
- 前端模块化架构设计与实现(二|模块接口设计)
- 模块化编程的实践者 disconver 更新了用户模块
- ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路
- JavaScript模块化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ant Colony Optimization
Marco Dorigo、Thomas Stützle / A Bradford Book / 2004-6-4 / USD 45.00
The complex social behaviors of ants have been much studied by science, and computer scientists are now finding that these behavior patterns can provide models for solving difficult combinatorial opti......一起来看看 《Ant Colony Optimization》 这本书的介绍吧!