Node.js 模块机制及常见面试问题解答

Node.js 模块机制采用了 Commonjs 规范,弥补了当前 JavaScript 开发大型应用没有标准的缺陷,类似于 Java 中的类文件,Python 中的 import 机制,Node.js 中可以通过 module.exports、require 来导出和引入一个模块.

在模块加载机制中,Node.js 采用了延迟加载的策略,只有在用到的情况下,系统模块才会被加载,加载完成后会放到 binding_cache 中。

面试指南

  • require的加载机制?,参考:模块加载机制
  • module.exports与exports的区别,参考:对象引用关系考察
  • 假设有a.js、b.js两个模块相互引用,会有什么问题?是否为陷入死循环?,参考正文“模块循环引用问题1”
  • a模块中的undeclaredVariable变量在b.js中是否会被打印?,参考正文“模块循环引用问题2”
  • 模块在require的过程中是同步还是异步?,参考正文模块加载机制 “文件模块“

模块的分类

系统模块

  • C/C++ 模块,也叫 built-in 内建模块,一般用于 native 模块调用,在 require 出去
  • native 模块,在开发中使用的 Node.js 的 http、buffer、fs 等,底层也是调用的内建模块 (C/C++)。

第三方模块

非 Node.js 自带的模块称为第三方模块,其实还分为路径形式的文件模块(以 .../开头的)和自定义的模块(比如 express、koa 框架、moment.js 等)

  • javaScript 模块:例如?hello.js
  • json 模块:例如?hello.json
  • C/C++ 模块:编译之后扩展名为 .node 的模块,例如?hello.node

目录结构

├── benchmark           一些 Node.js 性能测试代码
├── deps                Node.js 依赖
├── doc                 文档
├── lib                 Node.js 对外暴露的 js 模块源码
├── src                 Node.js 的 c/c++ 源码文件,内建模块
├── test                单元测试
├── tools               编译时用到的工具
├── doc                 api 文档
├── vcbuild.bat         win 平台 makefile 文件
├── node.gyp            node-gyp 构建编译任务的配置文件
...

模块加载机制

面试中可能会问到能说下require的加载机制吗?

在 Node.js 中模块加载一般会经历 3 个步骤, 路径分析文件定位编译执行

按照模块的分类,按照以下顺序进行优先加载:

  • 系统缓存:模块被执行之后会会进行缓存,首先是先进行缓存加载,判断缓存中是否有值。
  • 系统模块:也就是原生模块,这个优先级仅次于缓存加载,部分核心模块已经被编译成二进制,省略了?路径分析、?文件定位,直接加载到了内存中,系统模块定义在 Node.js 源码的 lib 目录下,可以去查看。
  • 文件模块:优先加载?.、?..、?/?开头的,如果文件没有加上扩展名,会依次按照?.js、?.json、?.node?进行扩展名补足尝试,那么在尝试的过程中也是以同步阻塞模式来判断文件是否存在,从性能优化的角度来看待,?.json、?.node最好还是加上文件的扩展名。
  • 目录做为模块:这种情况发生在文件模块加载过程中,也没有找到,但是发现是一个目录的情况,这个时候会将这个目录当作一个??来处理,Node 这块采用了 Commonjs 规范,先会在项目根目录查找 package.json 文件,取出文件中定义的 main 属性?("main":"lib/hello.js")?描述的入口文件进行加载,也没加载到,则会抛出默认错误: Error: Cannot find module ‘lib/hello.js‘
  • node_modules 目录加载:对于系统模块、路径文件模块都找不到,Node.js 会从当前模块的父目录进行查找,直到系统的根目录

require 模块加载时序图

模块缓存在哪

上面讲解了模块的加载机制,中间有提到模块初次加载之后会缓存起来,有没有疑问,模块缓存在哪里?

Node.js 提供了 require.cache API 查看已缓存的模块,返回值为对象,为了验证,这里做一个简单的测试,如下所示:

新建 test-module.js 文件

这里我导出一个变量和一个方法

module.exports={
      a : 1,
      test : () => { }
}

新建 test.js 文件

require('./test-module.js');
console.log(require.cache);

在这个文件里加载 test-module.js 文件,在之后打印下 require.cache 看下里面返回的是什么?看到以下结果应该就很清晰了,模块的文件名、地址、导出数据都很清楚。

模块循环引用

问题1

假设有 a.js、b.js 两个模块相互引用,会有什么问题?是否为陷入死循环?看以下例子

// a.js
console.log('a模块start');
exports.test = 1;
undeclaredVariable = 'a模块未声明变量'
const b = require('./b');
console.log('a模块加载完毕: b.test值:',b.test);

// b.js
console.log('b模块start');
exports.test = 2;
const a = require('./a');
console.log('undeclaredVariable: ', undeclaredVariable);
console.log('b模块加载完毕: a.test值:', a.test);

问题2

a 模块中的 undeclaredVariable 变量在 b.js 中是否会被打印?

控制台执行 node a.js,查看输出结果:

a模块start
b模块start
undeclaredVariable: a模块未声明变量
b模块加载完毕: a.test值: 1
a模块加载完毕: b.test值: 2

问题1,启动 a.js 的时候,会加载 b.js,那么在 b.js 中又加载了 a.js,但是此时 a.js 模块还没有执行完,返回的是一个 a.js 模块的 exports 对象 未完成的副本 给到 b.js 模块(因此是不会陷入死循环的)。然后 b.js 完成加载之后将 exports 对象提供给了 a.js 模块

问题2,因为 undeclaredVariable 是一个未声明的变量,也就是一个挂在全局的变量,那么在其他地方当然是可以拿到的。

在执行代码之前,Node.js 会使用一个代码封装器进行封装,例如下面所示:

(function(exports, require, module, __filename, __dirname) {
    // 模块的代码
});

对象引用关系考察

也许是面试考察最多的问题:module.exports 与 exports 的区别?

exports 相当于 module.exports 的快捷方式如下所示:

const exports = modules.exports;

但是要注意不能改变 exports 的指向,我们可以通过 exports.test=‘a‘ 这样来导出一个对象, 但是不能向下面示例直接赋值,这样会改变 exports 的指向

 // 错误的写法 将会得到 undefined
exports = {
     'a': 1,
     'b': 2
}
// 正确的写法
modules.exports = {
     'a': 1,
     'b': 2
}

更好的理解之间的关系,可以参考 JavaScript 中的对象引用?https://www.nodejs.red/#/javascript/object?

文章来源

转载自?Nodejs技术栈

原文地址:https://www.cnblogs.com/ghostxbh/p/11416578.html

时间: 2024-12-27 00:47:00

Node.js 模块机制及常见面试问题解答的相关文章

node的模块机制

Node.js模块的实现 之前在网上查阅了许多介绍Node.js的文章,可惜对于Node.js的模块机制大都着墨不多.在后续介绍模块的使用之前,我认为有必要深入一下Node.js的模块机制. CommonJS规范 早在Netscape诞生不久后,JavaScript就一直在探索本地编程的路,Rhino是其代表产物.无奈那时服务端JavaScript走的路均是参考众多服务器端语言来实现的,在这样的背景之下,一没有特色,二没有实用价值.但是随着JavaScript在前端的应用越来越广泛,以及服务端J

node.js零基础详细教程(4):node.js事件机制、node异步IO操作

第四章 建议学习时间3小时  课程共10章 学习方式:详细阅读,并手动实现相关代码 学习目标:此教程将教会大家 安装Node.搭建服务器.express.mysql.mongodb.编写后台业务逻辑.编写接口,最后完成一个完整的项目后台,预计共10天课程. node.js事件机制 node.js是单线程,但是通过事件和回调支持并发,可以实现非常高的性能. node.js所有的API都是通过异步调用.第一堂课的时候,我们写过一个同步和异步的示例(如下),当初说到:同步代码先执行完成,然后才执行异步

Node.js模块封装及使用

Node.js中也有一些功能的封装,类似C#的类库,封装成模块这样方便使用,安装之后用require()就能引入调用. 一.Node.js模块封装 1.创建一个名为censorify的文件夹 2.在censorify下创建3个文件censortext.js.package.json.README.md文件 1).在censortext.js下输入一个过滤特定单词并用星号代替的函数. var censoredWorlds=["sad","bad","mad&

Node.js 模块和 NPM

1.模块概念 原生模块:Node.js API 提供的原生模块,原生模块在启动时已经被加载. 文件模块:动态加载模块,由原生模块 module 来实现和完成. 文件模块需要通过调用 require 方法来实现加载. Node.js 对两种模块都有缓存,不会重复开销去加载模块,只读取相应数据. 原生模块的调用 Node.js 的 API require 加载相应的 Node.js 模块,加载成功后返回一个 Node.js 模块对象. 该对象拥有该模块的所有方法和属性. var httpModule

如何发布一个自定义Node.js模块到NPM(详细步骤)

咱们闲话不多说,直接开始! 由于我从没有使用过MAC,所以我不保证本文中介绍的操作与MAC一致. 文章开始我先假定各位已经在window全局安装了Node.js,下面开始进行详细步骤介绍: 本文本着,以极少的文字说明以及极少的代码书写为原则来给大家演示! 文章中上传的模块不具备任何意义! 一.封装node.js模块时的必须项 1.创建package.json 每一个完整封装的node模块,必须含有一个参数明确的package.json文件! 以下为package.json的最精简配置: { "n

node js 模块系统

Node.js模块系统 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'hello.js' 文件,代码如下: var hello = require('./hello'); hello.w

3:Node.js模块系统

原文出自:http://www.w3cschool.cc/nodejs/nodejs-module-system.html Node.js模块系统 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建

Developer - 如何自我保证Node.js模块质量

组里正在做SaaS产品,其中一些模块(Module)是Node.js实现,这里我们主要使用Node.js实现Web Server来提供服务. 在做SaaS项目之前,组里的开发模式是传统的Deverloper + QA的模式,这是传统的协作模式,Developer负责写代码开发,当然也会有基本的自测,QA负责测试,遇到问题,提Bug给Developer去修复,Developer修复Bug后,由QA来验证并记录Bug.但这样的协作模式已不适合SaaS产品的开发,SaaS产品更新迭代快,模块众多,这就

本地安装node.js模块

一.需求 单位电脑不让上网,但是需要用到一个node.js模块,elasticdump. 二.解决 1.自己电脑上下载模块: npm install elasticdump -g 注意:必须要加 -g,因为需要全局使用.非全局的模块复制到电脑上,还没找的方法运行模块. 2.复制拷贝 a.复制 操作1步骤会默认下载到C:\Users\T470s\AppData\Roaming\npm\node_modules\elasticdump ,复制elasticdump文件夹到U盘, 再到C:\Users