浅析nodejs的require函数分别加载自定义模块和npm开源库的不同加载原理、NodeJS模块加载机制require和module的理解

一、require 函数

1、require 函数是什么?

  首先,直接说require函数的功能:用来加载目标js库,并返回目标js库公开的属性成员函数/变量。

  我们在终端 node shell 输入  this.require === require,可以看到为 true

  由此可得出结论:require是Node引擎上下文context的内置对象属性,也就是全局对象的require属性,可调用或者使用this.require也行。

2、require 加载 node 内置模块

  我们都知道 require 可以用来加载 node 的内置模块。比如: require('fs') // 加载文件系统模块

3、那么非内置的模块,也想用require来加载,怎么做呢?

  在当前目录下,我们新建一个 test.js,内容如图;然后,试着用require来加载看看。

  require函数能够加载这个 test.js,不过不像内置模块一样,需要通过给文件路径来定位到该 js 文件,如:require('./test') 或 require('./test.js'),这里我们看到打印的对象没有任何属性,require返回值为 :{}  // 没有任何属性

二、require 函数加载原理

由于NodeJS模块都遵循了CommonJS规范,根据CommonJS规范,JS库的开发者如果需要开发某些函数对外部模块使用,需要使用module.exports或者exports

具体如下:module.exports.属性名 = 函数引用        // 这里将当前JS内的某个函数赋给 module.exports

或者:exports.属性名 = 函数引用

  上面那个 test.js 相当于

const products = {data:[]}
function getData(){
  return products.data;
}
//默认情况,module.exports 这个对象没有任何属性,如下代码
module.exports = {}

  修改 test.js 文件,继续在终端跑 require('./test') 看,输出仍旧为:{}

  为什么呢?我们明明修改了导出的内容啊?

  这里先说一下结论:我们需要先退出当前node终端,重新进入终端,才可以导入修改后的内容。这里请思考一下为什么需要重新打开一个node REPL终端呢?

  可以看到重新打开后,就可以获取到值了。再查看 require 发现上面多了很多东西。

三、怎么加载npm registry上的库?

  比如输入require('lodash') 马上发现错误了,“Cannot find module 'lodash'",这个错误经常容易见到(有时候拿到一个NodeJS项目忘记跑npm install了)

  通常我们需要使用命令安装JS库:npm install 目标JS库名,再来使用它共享的功能。

  我们试试看,安装完lodash库之后,继续在Node终端输入require('lodash') 可以使用了,这里不需要重启(这又是为什么呢?为什么不需要重启呢?)。

1、为什么 node 终端能够加载到 test.js,export内部函数之后又需要重启,引入外部JS库又不需要重开一个Node REPL终端?

  这里需要讲讲require的另一个伙伴:module函数。它跟require函数一样都挂载在上下文中,也是全局对象的一个属性,它的作用是管理整个项目的模块。

  进入node终端,打印 module,可以显示模块加载的细节,这里稍微留意一下children(里面就有我们的 test.js 文件)。

2、解答”Cannot find module"问题

  paths非空,我们使用require加载函数的时候,node引擎会从内置模块和paths对应的路径去查找模块,找不到才会抛出类似异常:“Cannot find module 'lodash'"

  当我们跑了npm install 库名,对应模块被下载到node_module目录,加载的时候才能定位到库,正常使用该库功能

  在含有package.json的目录中,执行npm install命令,可以一次性下载dependencies属性声明的全部依赖库

3、解答是否需要重启Node REPL?或者修改代码是否需要重启正在的NodeJS进程的问题?

  继续在终端输入require('./test') ,然后输入module, 再次输出module对象,它的children已经多了一个Module对象(id对应到了test.js)

  当我们修改了 test.js,再次 require("./test") 的时候,node引擎发现module对象已经记录加载过 test.js 了,不会重新进行加载。

  所以,这也就是虽然我们最新代码导出了getData函数,可是我们加载到的仍旧是:{}  无任何函数导出的原因。

4、解答为何npm install lodash之后能够直接在node终端直接require就可以生效?

  因为node启动,默认会查找到当前目录下的node_modules目录(不管目录存在不存在),当我们require一个不存在的js模块的时候,module对象找不到模块,它的children属性并不会有任何变动。

  当安装了 lodash 后,它就会在 node_modules 里存在,当再次去查找时就可以查到了。因此npm上的开源ks库,我们只需要安装了,就可以require加载。

5、总结:

  require和module互相协作产生的模块加载机制,是整个NodeJS开源文化的基石之一;而CommonJS就是一个脱离了框架的协议。这也在很多语言中反复出现,像python/java的import包,CommonJS就像一个包协议,约定了库的共享的标准格式,npm对标maven central/python libs。这套协议加上加载模式相关的接口模式,很值得借鉴。

posted @ 2022-04-14 22:23  古兰精  阅读(745)  评论(0编辑  收藏  举报