这篇模块化学不会,我回家养猪仔

96 阅读5分钟

R-C.jpg

什么是模块化?

  • 模块化就把一个程序分解成很多个小的结构,每一个结构都是一个模块,把他们组合起来就成了一个系统。(模块类似积木, 模块化开发类似搭积木)

  • 每个模块,有自己的作用域,可以将自己希望暴露的东西导出( 变量、函数 对象等)

  • 也可以导入另一个模块的内容(变量、函数 对象等)。

什么模块化开发 ?

  • 把程序分解成小的模块进行开发, 再他们组合起来过程就是 模块化开发

模块化开发的优点

  • 提高代码的复用率
  • 方便代码管理
  • 避免全局环境污染 (变量名、函数名、 对象名 冲突等)

流行的模块化规范

  • CommonJS (简称CJS)
  • AMD
  • CMD
  • ESModuole

CommonJS规范:

浏览器默认没有实现CommomJS规范, 在浏览器不能直接使用 Node环境中实现CommomJS规范, 可以直接使用

  • 模块导出内容: exports module.exports
  • 模块导入内容: require

CommonJS 例子

  1. 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暴露的对象修改是不规范的,不要这样做)

  1. 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

ES Module