碰到Cannot find module了吗? 来看看require函数与NodeJS模块加载

举报
雷学委 发表于 2021/12/19 19:07:16 2021/12/19
【摘要】 ​ 目录下面谈谈require函数先搞清楚是什么require能用来干什么?非内置的模块,也想用require来加载怎么做?require函数加载原理那么在npm registry上的库,怎么进行加载?好了,前面提了几个围绕了是否重新开一个NodeREPL终端来require JS库的问题解答”Cannot find module"问题解答是否需要重启Node REPL 或者修改代码是否需要...

 目录

下面谈谈require函数

先搞清楚是什么

require能用来干什么?

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

require函数加载原理

那么在npm registry上的库,怎么进行加载?

好了,前面提了几个围绕了是否重新开一个NodeREPL终端来require JS库的问题

解答”Cannot find module"问题

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

解答为何npm install lodash之后为何能够直接在node终端直接require

构建代码共享,开源文化

总结


在NodeJS项目开发过程中,我们经常使用公共JS库。

比较常用的做法就是通过npm去install目标js库,然后这个库会被放在node_modules目录下。

接着,我们自己写的JS文件中,使用require("目标js")库来使用他人共享的代码,这样很容易事半功倍。

下面谈谈require函数

本文会使用Node REPL:这是一个交互式的NodeJS代码执行终端,更多参考: 如何使用 Node.js REPL

先搞清楚是什么

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

我们打开terminal终端/Command,输入: node //打开Node REPL

然后输入下面内容:

require

this.require === require

所以这里得到结论:require是Node引擎上下文(context)的内置对象属性,也就是全局对象的require属性,可调用或者使用this.require也行。

require能用来干什么?

初学NodeJS会了解到它有内置模块,比如fs,http 等。好,这里我们试着require('fs') //加载文件系统模块

require('fs')

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

在当前目录下,我们编写一个product.js(内容如下)。然后,试着用require来加载看看。

const products = {data:[]}

function getData(){
  return products.data;
}

Node REPL终端输入:require('./product.js')

require函数能够加载这个product.js,不过不像内置模块一样,需要通过给路径来定位到js文件,如:require('./product') 或者 require('./product.js')

这里我们看到打印的对象没有任何属性,require返回值为 :{}  //没有任何属性。

require函数加载原理

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

具体如下:

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

或者:

exports.属性名 = 函数引用 

像前面一个版本的product.js相当于

const products = {data:[]}

function getData(){
  return products.data;
}

//默认情况,module.exports 这个对象没有任何属性,如下代码。
module.exports = {}

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

这里需要退出当前node终端,重新进入(请读者带着一个思考题:为什么需要重新打开一个node REPL终端)。

那么在npm registry上的库,怎么进行加载?

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

也可以输入require('restify'), require加载一些常见的模块试试。

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

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


好了,前面提了几个围绕了是否重新开一个NodeREPL终端来require JS库的问题

为何node终端能够加载到product.js, export内部函数之后又需要重启,引入外部JS库又不需要重开一个Node REPL终端。。。

这里需要讲讲require的另一个伙伴,module函数。

它跟require函数一样都挂载在上下文中,也是全局对象的一个属性,它的作用是管理整个项目的模块。

上面所示,在一个有product.js 和node_modules/lodash这个模块,进入node终端,打印module显示的了模块加载的细节,这里稍微留意一下children(目前是一个空的数组)。

解答”Cannot find module"问题

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

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

在含有package.json的目录中,执行npm install命令,可以一次性下载dependencies属性声明的全部依赖库,在我们写的js文件中能够正常使用。

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

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

当我们修改了product.js的时候,node引擎发现module对象已经记录加载过product.js了,不会重新进行加载。所以,虽然最新代码导出了getData函数,可是我们加载到的仍旧是:{}//无任何函数导出。

解答为何npm install lodash之后为何能够直接在node终端直接require

这个很简单,因为node启动,默认会查找到当前目录下的node_modules目录(不管目录存在不存在)。

当我们require一个不存在的js模块的时候,module对象找不到模块,它的children属性并不会有任何变动。

所以只需要安装了,就可以require加载。

构建代码共享,开源文化

这算是个题外话,前面有文章写过关于实现商品的增删查改,NodeJS 后端开发 03 使用Restify开发API 一个完整的CRUD_持续学习持续开发,我是雷学委!-CSDN博客

读者可以下载这个JS:product.js · master · 雷学委 / nodejs-api-002-restify-crud · GIT CODE,使用require调用就能获得对商品对增删查改的功能。

通过对代码进行封装,对外开放几个函数,隐藏了细节,也简化了对功能的使用。

var pm = require('./product')

//调用库开放函数
pm.getData()

//其他调用
pm.其他函数()

好用的代码像诗,越简洁,越好用,那些垃圾代码只会慢慢沉寂下来无人问津。


npm registry (npm

在npm registry上面有很多开发者发布的库,当我们想要使用一个功能,或者实现一个功能之前,不妨先上去找找有没有别人写好的库。

如果没有,那么遵循CommonJS(前面说的module.exports)来组织我们的代码发布共享,解决问题同时帮助他人,微薄之力能够推动社区进步呢。

npm社区就这样通过一个个普通开发者,分享一个个的小的库,培养了一个丰富多彩的技术生态。

Atwood’s Law是Jeff Atwood在2007年提出的:“any application that can be written in JavaScript, will eventually be written in JavaScript.”

这里我们在简单大胆的总结,require和module互相协作产生的模块加载机制,是整个NodeJS开源文化的基石之一;而CommonJS就是一个脱离了框架的协议。

这也在很多语言中反复出现,像python/java的import包,CommonJS就像一个包协议约定了库的共享的标准格式,npm对标maven central/python libs。

这套协议加上加载模式相关的接口模式,很值得借鉴。

总结

本文简单的介绍了require和module函数,Node引擎内使用module来管理模块,使用require加载模块。

基于这个技术和协议延伸了:如何做代码分享,构建JS模块分享的生态。

本篇从使用函数反向思考简单涉猎,更多细节可以自行阅读NodeJS源码。

篇幅有限,后续会发表更多技术补充。细心的读者可以发现,本文对于加载lodash这个模块没有更多深入解析,这种发布在npm registry上的包的解析,可以自行阅读它的代码,这一块还是比较好玩的。

参考:

What is require? | Node.js

modules - Node.js Manual & Documentation (这里的模块加载方式已经过时,不过刚好没有找到module的解析,可以看看,原理类似)

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。