什么是模块化?
-
模块化就把一个程序分解成很多个小的结构,每一个结构都是一个模块,把他们组合起来就成了一个系统。(模块类似积木, 模块化开发类似搭积木)
-
每个模块,有自己的作用域,可以将自己希望暴露的东西导出( 变量、函数 对象等)
-
也可以导入另一个模块的内容(变量、函数 对象等)。
什么模块化开发 ?
- 把程序分解成小的模块进行开发, 再他们组合起来过程就是 模块化开发
模块化开发的优点
- 提高代码的复用率
- 方便代码管理
- 避免全局环境污染 (变量名、函数名、 对象名 冲突等)
流行的模块化规范
- CommonJS (简称CJS)
- AMD
- CMD
- ESModuole
CommonJS规范:
浏览器默认没有实现CommomJS规范, 在浏览器不能直接使用
Node环境中实现CommomJS规范, 可以直接使用
- 模块导出内容:
exportsmodule.exports - 模块导入内容:
require
CommonJS 例子
- exports-require
add.js
const add = function (a,b) {
return a + b
}
const OrderStatuList = [
{value: '111', label: '已下单'},
{value: '222', label: '已到达'},
{value: '333', label: '已送车'},
{value: '444', label: '已取消'},
]
lets ORDER_ON = '11111134R54T4T5'
exports.add = add
exports.OrderStatuList = OrderStatuList
exports.ORDER_ON = ORDER_ON
main.js
let {OrderStatuList, ORDER_ON, add} = require('./add.js')
console.log(OrderStatuList);
console.log(ORDER_ON);
console.log(add(1,2));
CommonJS的实现的本质
exports={}
- exports 默认是个空对象,给exports添加很多属性,把这些属性导出
let add = require('.add.js')
-
导入的本质就是对象引用赋值,把exports对象的地址赋值给了add (add 就是 exports对象)
-
add.ORDER_ON === 'zyb'; exports 对象也会改变 (通过add暴露的对象修改是不规范的,不要这样做)
- module.exports-require
add.js
const add = function (a,b) {
return a + b
}
const OrderStatuList = [
{value: '111', label: '已下单'},
{value: '222', label: '已到达'},
{value: '333', label: '已送车'},
{value: '444', label: '已取消'},
]
lets ORDER_ON = '11111134R54T4T5'
module.exports.add = add
module.exports.OrderStatuList = OrderStatuList
module.exports.ORDER_ON = ORDER_ON
main.js
let {OrderStatuList, ORDER_ON, add} = require('./add.js')
console.log(OrderStatuList);
console.log(ORDER_ON);
console.log(add(1,2));
打印的结果: module.exports和 exprots 导出是一样的。
结论
-
Node中使用的是Module类型(函数构造的),每个js文件,每个模块都是Module的一个实例, 也就是module;
-
Node真正导出的根本不是exports, 而是module.exports, module才是导出的真正实现者,只是默认情况下,exports,和module的属性exports 引用了同一个对象。
-
开发中我们经常用 module.exports = {}, module.export引用的一个新的对象地址, exports就没有意义了。exports 还 指向那个老对象地址, module.exports 和 exports 的关系仅此而已。
-
module.exports 和 exprots 默认都指向同一个空对象,也就是
module.exports 恒等于 exprots修改了这个对象, module.exports和exports 都会改变
require 的细节
- require是一个函数,可以导入我没写的模块
- 可以导入内置模块 (path等等)
const path = require('path')
导入查找规则
格式如下:require(X) 不带后缀名
情况一
情况一:X是一个Node核心模块,比如path、http 直接返回核心模块,并且停止查找
情况二
情况二:X是以 ./ 或 ../ 或 /(根目录)开头的 ◼ 第一步:将X当做一个文件在对应的目录下查找; 1.如果有后缀名,按照后缀名的格式查找对应的文件 2.如果没有后缀名,会按照如下顺序: ✓ 1> 直接查找文件X ✓ 2> 查找X.js文件 ✓ 3> 查找X.json文件 ✓ 4> 查找X.node文件 ◼ 第二步:没有找到对应的文件,将X作为一个目录 查找目录下面的index文件 ✓ 1> 查找X/index.js文件 ✓ 2> 查找X/index.json文件 ✓ 3> 查找X/index.node文件 ◼ 如果没有找到,那么报错:not found
情况三
直接是一个X(没有路径),并且X不是一个核心模块, 一直向上查找, 如果上面的路径中都没有找到,那么报错:not found
模块加载的过程
论一:模块在被第一次引入时,模块中的js代码会被运行一次 ◼ 结论二:模块被多次引入时,会缓存,最终只加载(运行)一次 为什么只会加载运行一次呢? 这是因为每个模块对象module都有一个属性:loaded。 为false表示还没有加载,为true表示已经加载; ◼ 结论三:如果有循环引入,那么加载顺序是什么? ◼ 如果出现右图模块的引用关系,那么加载顺序是什么呢? 这个其实是一种数据结构:图结构; 图结构在遍历的过程中,有深度优先搜索(DFS, depth first search)和广度优先搜索(BFS, breadth first search); Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb
CommonJS规范缺点
CommonJS加载模块是同步的: 同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行; 这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快; ◼ 如果将它应用于浏览器呢? 浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行; 那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作; ◼ 所以在浏览器中,我们通常不使用CommonJS规范: 当然在webpack中使用CommonJS是另外一回事; 因为它会将我们的代码转成浏览器可以直接执行的代码; ◼ 在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD: 但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换; AMD和CMD已经使用非常少了;
AMD规范
- AMD主要是应用于浏览器的一种模块化规范:
- 它采用的是异步加载模块;
AMD规范常用由require.js实现
CMD规范
-
CMD规范也是应用于浏览器的一种模块化规范:
-
它也采用的也是异步加载模块,
-
CMD也有自己比较优秀的实现方案: SeaJS