模块化
模块化是一种思想, 是将大工程拆成小的模块分治的思想.
服务端模块化
Nodejs和CommonJS的关系
这里要说一下Nodejs和CommonJS的关系。
- Nodejs的模块化能一种成熟的姿态出现离不开CommonJS的规范的影响
- 在服务器端CommonJS能以一种寻常的姿态写进各个公司的项目代码中,离不开Node的优异表现
- Node并非完全按照规范实现,针对模块规范进行了一定的取舍,同时也增加了少许自身特性
CommonJS规范简介
CommonJS对模块的定义非常简单,主要分为模块引用,模块定义和模块标识(require()函数)3部分
意义在于将类聚的方法和变量等限定在私有的作用域中,同时支持引入和导出将上下游模块无缝衔接,每个模块具有独立的空间,它们互不干扰。
Nodejs的模块化实现
Node模块在实现中并非完全按照CommonJS来,进行了取舍,增加了一些自身的的特性。
Node中一个文件是一个模块——module
一个模块就是一个Module的实例
Node模块分类
Node模块一般分为两种核心模块和文件模块。
核心模块——就是Node内置的模块比如http, path等。在Node的源码的编译时,核心模块就一起被编译进了二进制执行文件,部分核心模块(内建模块)被直接加载进内存中。
在Node模块的引入过程中,一般要经过一下三个步骤
- 路径分析 2. 文件定位 3. 编译执行
核心模块会省略文件定位和编译执行这两步,并且在路径分析中会优先判断,加载速度比一般模块更快。
文件模块——就是外部引入的模块如node_modules里通过npm安装的模块,或者我们项目工程里自己写的一个js文件或者json文件。
文件模块引入过程以上三个步骤都要经历。
前端模块化
前端模块化和服务端模块化有什么区别
服务端加载一个模块,直接就从硬盘或者内存中读取了,消耗时间可以忽略不计 浏览器需要从服务端下载这个文件,所以说如果用CommonJS的require方式加载模块,需要等代码模块下载完毕,并运行之后才能得到所需要的API。
为什么CommonJS不适用于前端模块
如果我们在某个代码模块里使用CommonJS的方法require了一个模块,而这个模块需要通过http请求从服务器去取,如果网速很慢,而CommonJS又是同步的,所以将阻塞后面代码的执行,从而阻塞浏览器渲染页面,使得页面出现假死状态。
因此后面AMD规范随着RequireJS的推广被提出,异步模块加载,不阻塞后面代码执行的模块引入方式,就是解决了前端模块异步模块加载的问题。
AMD(Asynchronous Module Definition) & RequireJS
AMD——异步模块加载规范 与CommonJS的主要区别就是异步模块加载,就是模块加载过程中即使require的模块还没有获取到,也不会影响后面代码的执行。
RequireJS——AMD规范的实现。其实也可以说AMD是RequireJS在推广过程中对模块定义的规范化产出。
//a.js
define(function(){
console.log('a.js执行');
return {
hello: function(){
console.log('hello, a.js');
}
}
});
//b.js
require(['a'], function(a){
console.log('b.js 执行');
a.hello();
$('#b').click(function(){
b.hello();
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CommonJS 和AMD的对比:
CommonJS一般用于服务端,AMD一般用于浏览器客户端
CommonJS和AMD都是运行时加载
什么是运行时加载
CommonJS 和AMD模块都只能在运行时确定模块之间的依赖关系
require一个模块的时候,模块会先被执行,并返回一个对象,并且这个对象是整体加载的
CMD(Common Module Definition) & SeaJS
CMD——通用模块规范,由国内的玉伯提出。
SeaJS——CMD的实现,其实也可以说CMD是SeaJS在推广过程中对模块定义的规范化产出。
与AMD规范的主要区别在于定义模块和依赖引入的部分。AMD需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块内容中:
define(['dep1', 'dep2'], function(dep1, dep2){
return function(){};
})
define(function(require, exports, module){
//依赖模块a
var a = require('./a');
//调用模块a的方法
a.method();
})
2
3
4
5
6
7
8
9
10
11
在依赖示例部分,CMD支持动态引入,require、exports和module通过形参传递给模块,在需要依赖模块时,随时调用require( )引入即可,
也就是说与AMD相比,CMD推崇依赖就近, AMD推崇依赖前置。
UMD(Universal Module Definition) 通用模块规范
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? module.exports = factory() // Node , CommonJS
: typeof define === 'function' && define.amd
? define(factory) //AMD CMD
: (global.CodeMirror = factory()); //模块挂载到全局
}(this, (function () {
...
})
2
3
4
5
6
7
8
9
可以看说所谓的兼容模式是将几种常见模块定义方式都兼容处理。
ES6模块
CommonJS和AMD都是运行时加载。ES6在语言规格层面上实现了模块功能,是编译时加载,完全可以取代现有的CommonJS和AMD规范,可以成为浏览器和服务器通用的模块解决方案。
ES6模块与CommonJS模块加载区别
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以说ES6是编译时加载,不同于CommonJS的运行时加载(实际加载的是一整个对象),ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式:
//ES6模块
import { basename, dirname, parse } from 'path';
//CommonJS模块
let { basename, dirname, parse } = require('path');
2
3
4
5
以上这种写法与CommonJS的模块加载有什么不同?
当require path模块时,其实 CommonJS会将path模块运行一遍,并返回一个对象,并将这个对象缓存起来,这个对象包含path这个模块的所有API。以后无论多少次加载这个模块都是取这个缓存的值,也就是第一次运行的结果,除非手动清除。
ES6会从path模块只加载3个方法,其他不会加载,这就是编译时加载。ES6可以在编译时就完成模块加载,当ES6遇到import时,不会像CommonJS一样去执行模块,而是生成一个动态的只读引用,当真正需要的时候再到模块里去取值,所以ES6模块是动态引用,并且不会缓存值。
因为CommonJS模块输出的是值的拷贝,所以当模块内值变化时,不会影响到输出的值。