Skip to content
本页目录

node 模块化

没有模块化带来的问题

早期没有模块化带来了很多的问题:比如命名冲突的问题

当然,我们有办法可以解决上面的问题:立即函数调用表达式(IIFE)IIFE (Immediately Invoked Function Expression) 但是,我们其实带来了新的问题:

  • 第一,我必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用。
  • 第二,代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写。
  • 第三,在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况。

CommonJS 和 Node

我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为 ServerJS,后来为了 体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS。

  • Node 是 CommonJS 在服务器端一个具有代表性的实现
  • Browserify 是 CommonJS在浏览器中的一种实现。
  • webpack 打包工具具备对 CommonJS 的支持和转换。

所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发

  • 在Node中每一个js文件都是一个单独的模块。
  • 这个模块中包括CommonJS规范的核心变量:exports、module.exports、require。
  • 我们可以使用这些变量来方便的进行模块化开发。

前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现。

  • exports和module.exports可以负责对模块中的内容进行导出。
  • require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容。

exports 和 require

exports 和 module.exports 与 require() 的关系

  • exports 就是一个对象,往外导出东西的时候直接可以往上面挂就可以了。for example:
js
// 就是一个模块
// 1.模块内部定义东西
const name = 'jingjing'
const age = 18;
let message = "my name is why";
function sayHello(name) {
  console.log("Hello " + name);
}


// 通过exports导出内容
exports.name = name;
exports.age = age;
exports.sayHello = sayHello;
  • CommonJS 中是没有 module.exports 的概念的;

  • 但是为了实现模块的导出,Node 中使用的是 Module的 类,每一个模块都是 Module 的一个实例,也就是 module。

  • 所以在 Node 中真正用于导出的其实根本不是 exports,而是 module.exports。

  • require() 和 exports 还有 module.exports 三个其实共同引用着同一个内存地址。但是当 module.exports 直接导出一个对象的时候,require() 就只能得到这个对象了,exports导出的东西就没有任何作用。(本质都是内存的知识)

  • 其实真正负责导出东西的是 module.exports,而不是 exports。之所以 exports 也能向外导出东西是因为 node 在源码中执行了特殊处理,让 module.exports = exports。(这样做的原因就是兼容 CommonJS的特点)。

require() 函数的处理细节

  • 某个模块第一次使用 require() 函数引入的时候,模块中的代码会先加载一遍。

  • 模块如果被多次引入的时候,只会加载一遍。因为每个模块对象 module 里面都有一个属性 loaded,为false表示还没有加载,为true则表示已经加载过了,就不会再重复加载了。

  • 我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导入的对象。

  • 那么,require的查找规则是怎么样的呢?https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together

这里我总结比较常见的查找规则:导入格式如下:require(X)

情况一:X是一个核心模块,比如 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不是一个核心模块

  • 这时候会去当前文件的 module 对象上的 path 数组里面对应的node_modules里面找。

CommonJS 规范缺点

CommonJS 加载模块是同步的。

  • 同步意味着只有等到引入的模块加载完了,才会执行当前模块的内容。

  • 这个如果在服务器中不会有问题,因为服务器加载的js文件都是本地文件,加载速度非常快。

在浏览器使用 CommonJS 规范?

  • 浏览器加载 js 文件需要先从服务器将文件下载下来,之后再加载运行。

  • 那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作。

所以早期在浏览器中是不使用 CommonJS 规范的

  • 在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD。

  • 目前一方面现代的浏览器已经支持ES Modules,另一方面借助于 webpack 等打包工具可以实现对 CommonJS或者 ES Module代码的转换。所以现在AMD和CMD已经使用非常少了。

模块化演变

AMD

  • 代表的有 require.js

CMD

  • 代表的有 sea.js

ES_Module

  • import 是一个关键字,只可以在代码加载(编译)过程中引入模块。

  • import() 是一个函数,可以在代码运行中引入模块。(动态加载)

  • 采用ES Module将自动采用严格模式:use strict。

  • export {} 不是对象,只是特定的语法。

MIT Licensed