require 方法详解

2023-11-10

在 NodeJS 中有一个方法是我们使用频率最高的,那就是 require 方法。NodeJs 遵循 CommonJS 规范,该规范的核心是通过 require来加载其他依赖的模块。

几个问题

  1. module.exports 或者 exports 是全局变量吗?
  2. 模块的加载是同步还是异步?
  3. 循环引用会不会产生性能问题或者导致错误?

什么是 CommonJS

每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即module.exports)是对外的接口。

Node 模块的分类

  1. build-in modules —— Nodejs 中以 C++ 形式提供的模块。
  2. constant module —— Nodejs 中定义常量的模块。
  3. native module —— Nodejs 中以 javascript 形式提供的模块。
  4. 第三方module —— 由第三方提供的模块。

module 对象

NodeJs 内部提供一个 Module 构建函数。所有模块都是 Module 的实例。

每个模块内部,都有一个 module 对象,代表当前模块。它有以下属性。

  • module 对象的属性

    • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
    • module.filename 模块的文件名,带有绝对路径。
    • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
    • module.parent 返回一个对象,表示调用该模块的模块(程序入口文件的module.parent为null)
    • module.children 返回一个数组,表示该模块要用到的其他模块。
    • module.exports 表示模块对外输出的值。
  • module.exports 属性
    module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。

  • exports 变量

我们有时候会这么写:

// test.js
function test(){
    console.log(test);
}
export.test = test;

// result.js
const test = require("./test")

这样也可以拿到正确的结果,这是因为:exports 变量指向 module.exports。这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;

注意:不能直接给 exports 变量赋值,这样会改变 exports 的指向,不再指向 module.exports。在其他模块使用 require 方法是拿不到赋给 exports 的值的,因为 require 方法获取的是其他模块的 module.exports 的值

建议:尽可能的使用 module.exports 来导出结果。

模块的流程

  • 创建模块
  • 导出模块
  • 加载模块
  • 使用模块

require 方法

require 是 node 用来加载并执行其它文件导出的模块的方法。

在 NodeJs 中,我们引入的任何一个模块都对应一个 Module 实例,包括入口文件。

完整步骤:

  1. 调用父模块的 require 方法(父模块是指调用模块的当前模块)
require = function require(path) {
    return mod.require(path);
};
  1. 调用 Module 的 _load 方法

  2. 通过 Module._resolveFilename 获取模块的路径 fileName

const filename = Module._resolveFilename(request, parent, isMain);
  1. 根据 fileName 判断是否存在该模块的缓存

    • 如果存在缓存,则调用 updateChildren 方法在更新缓存内容,并返回缓存
    • 如果不存在缓存,则继续执行
  2. 当做原生模块,调用 loadNativeModule 方法进行加载

    • 如果加载成功,则返回该原生模块
    • 否则,继续执行
  3. 根据当前模块名(路径)和父模块对象生成一个 Module 实例:

const module = cachedModule || new Module(filename, parent);
  1. 再判断该模块是否是入口文件
if (isMain) {
    process.mainModule = module;
    module.id = '.';
}
  1. 将该模块的实例存入到 Module 的缓存中
Module._cache[filename] = module;

image

  1. 该模块的实例调用自身的 load 方法,根据 fileName 加载模块
module.load(filename);
  1. 获取该模块文件的后缀名称
const extension = findLongestRegisteredExtension(filename);

如果后缀名称是ES Module格式的(.mjs),则判断Module是否支持.mjs文件的解析,如果不支持,则抛出异常。

  1. 根据后缀名称解析模块文件内容
Module._extensions[extension](this, filename);
  1. 根据fileName读取文件内容
content = fs.readFileSync(filename, 'utf8');
  1. 编译并执行读取到的文件,调用 module 自身的 _complile 方法:
module._compile(content, filename);

_compile 主要内容步骤:

const compiledWrapper = wrapSafe(filename, content, this);
const dirname = path.dirname(filename);
const require = makeRequireFunction(this, redirects);
let result;
const exports = this.exports;
const thisValue = exports;
const module = this;
result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname);
return result;

wrapSafe方法的返回值

image

具体获得上图结果的代码是:

const wrapper = Module.wrap(content);
return vm.runInThisContext(wrapper, {
    filename,
    lineOffset: 0,
    displayErrors: true,
    importModuleDynamically: async (specifier) => {
        const loader = asyncESM.ESMLoader;
        return loader.import(specifier, normalizeReferrerURL(filename));
    },
});
  1. 修改该模块的加载状态为true
this.loaded = true;
  1. 加载成功。

总结

通过上面的调试过程可得出以下结论:

  1. 在NodeJs中,从入口文件开始,一切皆 Module
  2. 模块的加载是同步的。
  3. 由于缓存机制的存在,模块的循环引用对性能的影响微乎其微,并且循环引用到的模块可能是不完整的,并且可能会导致错
  4. require 查找模块的流程如下:

image

  1. 文件路径的解析流程图如下:

image

本文完

学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!

大家好!我是〖编程三昧〗的作者 隐逸王,我的公众号是『编程三昧』,欢迎关注,希望大家多多指教!

知识与技能并重,内力和外功兼修,理论和实践两手都要抓、两手都要硬!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

require 方法详解 的相关文章

  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • Three.js:缩放几何图形后错误的 BoundingBox

    在我的场景中 我有一个简单的立方体 var test new THREE Mesh new THREE CubeGeometry 10 10 10 new THREE MeshBasicMaterial scene add test 该立方
  • 摩卡 - Chai Karma“套件未定义”

    我对 jscript tdd 很陌生 遇到了问题 希望有人能告诉我我在做什么 在浏览器中运行测试 通过 HTML 文件 一切正常 通过节点和业力运行它们我得到以下异常 我想在 node js 主机的 karma 中使用 Mocha 和 Ch
  • 可以在初始 DOM 解析期间/之前修改 DOM 吗?

    是否可以在初始 DOM 解析期间或之前修改 DOM 或者我是否必须等到 DOM 被解析和构建之后才能与其交互 更具体地说 是否有可能阻止 DOM 中的脚本元素使用用户脚本 内容脚本或 Chrome 或 Firefox 中的类似脚本运行 在解
  • Angular - CSS - 自定义类型=文件输入,如何使用按钮而不是标签?

    我制作了一个类型为 file 的自定义输入字段 因为我不喜欢默认的输入字段 为了实现这一目标 我做了
  • 设置 cookie 时中断 JavaScript 执行

    当设置 cookie 时 是否可以始终中断浏览器开发人员工具中的 javascript 执行 无需显式设置 JS 断点 document cookie 在 html head 块的开头添加此代码片段效果很好
  • 如何制作没有 ng-repeat 的模板并使用 Angular-drag-and-drop-lists 将数据传递到 $scope?

    我想用角度拖放列表 https github com marceljuenemann angular drag and drop lists使用我自己的网格模板到所见即所得编辑器 如何构建我自己的 HTML 模板而不需要ng repeat因
  • 如何解决 Typescript 构建中的错误“找不到模块 'jquery'”

    我目前在 ts 文件的顶部有这个import require jquery 我这样做是因为我试图在我的打字稿文件中使用 jquery 但我似乎无法编译它 因为它返回标题中所述的错误 我正在使用 ASP NET CORE 脚本文件夹 tsco
  • Javascript split 不是一个函数

    嘿朋友们 我正在使用 javascript sdk 通过 jQuery facebook 多朋友选择器在用户朋友墙上发布信息 但是我收到此错误friendId split 不是函数 这是我的代码 function recommendToFr
  • window.location 和 location.href 之间的区别

    我对之间的区别感到困惑window location and location href 两者似乎都以相同的方式行事 有什么不同 window location是一个对象 它保存有关当前文档位置的所有信息 主机 href 端口 协议等 lo
  • Firebase 函数 onWrite 未被调用

    我正在尝试使用 Firebase 函数实现一个触发器 该触发器会复制数据库中的一些数据 我想观看所有添加的内容votes user vote 结构为 我尝试的代码是 const functions require firebase func
  • 如何使用 crypto-js 解密 AES ECB

    我正在尝试将加密数据从 flash 客户端 发送到服务器端的 javascript 在 asp 中作为 jscript 运行 有几个 javascript Aes 库 但它们实际上没有文档记录 我正在尝试使用 crypto js 但无法让代
  • Javascript - 水波纹效果

    我需要 JS 上的脚本 它将以 水波纹 样式更改 images html 抱歉 6MB GIF 文件 http fcuunited ru temp listening2 gif http fcunited ru temp listening
  • 带参数的事件监听器

    我想将参数传递给 JavaScript 中的事件侦听器 我已经找到了解决方案 但我无法理解它们为什么或如何工作以及为什么其他解决方案不起作用 我有 C C 背景 但是 Javascript 函数的执行有很大不同 您能否帮助我理解以下示例如何
  • 用于交互式图形绘制的轻量级 JavaScript 库? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有兴趣了解用于绘制交互式图表的最轻量级 javascript 库 我掌握的数据主要是与海洋研究相关的科学数据 我知道一些 jquery
  • Javascript Replace() 和 $1 问题

    我正在尝试创建一个脚本来搜索文本中的模式并在它找到的字符串周围包裹一个标签 shop attributes td each function this html function i html return html replace E 0
  • 从 FileReader 设置背景图像样式

    我正在寻找一种解决方案 允许我从文件上传输入中获取文件并通过设置 document body style backgroundImage 来预览它 以下代码用于在 Image 元素中显示预览 function setImage id tar
  • 使用 MongoDB 和 Nodejs 插入和查询日期

    我需要一些帮助在 mongodb 和 nodejs 中按日期查找记录 我将日期添加到抓取脚本中的 json 对象 如下所示 jsonObj last updated new Date 该对象被插入到 mongodb 中 我可以看到如下 la
  • 如何确定所有角度2分量都已渲染?

    当所有 Angular2 组件完成渲染时 是否会触发一个角度事件 For jQuery 我们可以用 function 然而 对于 Angular2 当domready事件被触发 html 只包含角度组件标签 每个组件完成渲染后 domrea
  • 如何使用asm.js进行测试和开发?

    最近我读到asm js规范 看起来很酷 但是是否有任何环境 工具来开发和测试这个工具 这还只是处于规范阶段吗 您可以尝试使用 emscripten 和 ASM JS 1 并从侧分支在 firefox 构建中运行它 有关 asm js 的链接

随机推荐

  • 这篇文章教大家怎么生成ai图片

    在数字化时代 人工智能技术的发展正在改变我们的生活方式 其中之一就是在艺术领域的应用 ai绘画是人工智能技术在艺术领域的一种应用 它可以自动创作出各种各样的图片 为艺术家和设计师提供了更加便捷和高效的绘画工具 ai绘画的出现 不仅可以缩短绘
  • 素数环(回溯算法)

    回溯算法 在包含问题的所有可能解的解空间树中 从根节点出发 按照深度优先遍历的策略进行搜索 对于解空间树种的某个节点 如果该节点满足问题的约束条件 则进入该子树继续进行搜索 否则将以该节点为根节点的子树进行剪枝 回溯法常常可以避免所有的可能
  • layui table.js表格一直返回数据异常

    1 排查数据是否已经正常返回 2 layui table 返回格式默认不能自定义的 返回的分页json格式需要和table js中规定的返回键一致 如下 3 经过测试 其实最重要的是code需要和上图中statusName后的resultC
  • Cisco 路由器VOIP 配置解析

    在企业网络中推广 IP 语音技术有很多优点 例如可以控制数据流量 保证语音质量 充分利用企业租用的数据线路资源 节省传统的长途话费等等 企业使用 IP 语音技术 可以将语音 数据和多媒体通信融合在一个集成的网络中 并在一个企业解决方案中 把
  • 简易版的飞机大战(C语言)

    一 只会发射激光 画质不清晰的飞机大战 游戏的总体结构根据C语言的循环制作的 本来还想说点什么但是注释里面都有 代码 include
  • ansys18安装以后打不开_ansys18.0安装过程及常见问题解决方案【图文】

    1 首先打开ansys18 0安装文件夹 一般情况下通过网络渠道下载的ansys18 0安装包会有四个文件夹 crack文件夹为授权配置文件夹 disk1 disk2 disk3文件夹为安装程序包 我们首先打开disk1文件夹 双击setu
  • 物联网LoRa系列-31:通过LoRa终端实现远程抄表的原理与系统框架(水、电、气、热等通用)

    LoRa终端远程抄表的系统架构图 抄表系统由 无线电表 线集中器 业务数据中心组成 1 无线电表 又称为LoRa终端 内嵌LoRa模块 进行数据的采集 并LoRa WAN协议实现远程数据的传输 LoRa智能终端能将传统水表 电表等读数通过电
  • 可迭代(iterable)和类数组(array-like)

    可迭代 iterable 和类数组 array like 可迭代 iterable 是实现了 Symbol iterator 方法的对象 可以应用 for of 的对象被称为 可迭代的 类数组 array like 是有索引和 length
  • Redis主从复制的原理

    更多内容 欢迎关注微信公众号 全菜工程师小辉 公众号回复关键词 领取免费学习资料 在Redis集群中 让若干个Redis服务器去复制另一个Redis服务器 我们定义被复制的服务器为主服务器 master 而对主服务器进行复制的服务器则被称为
  • pyautogui库的使用教程(超详细)

    一 前言 PyAutoGUI 让您的 Python 脚本控制鼠标和键盘以自动与其他应用程序交互 官方文档 PyAutoGUI documentation 常用函数列表 函数名 功能 基本 pyautogui size 返回包含分辨率的元组
  • 在编辑操作时,el-select多选下拉组件,选中label标签后,框中无法回显选中的label,,,

    1 问题描述 在编辑操作时 页面的el select多选下拉组件 在选择新的label标签时 change事件和监听数组对象都能确定数据已发生改变 ngmodel绑定就是最新的id集合 但就是框中不显示最新选中的label 而change事
  • 论文导读

    图的最大独立集问题 MIS problem 是图论研究中的一个重要问题 具有广泛的应用 本文介绍了最大独立集求解相关的三篇工作 包括一篇启发式方法和两篇基于学习的方法 希望能让大家对这个问题有所了解 问题定义 一个图G V E 的顶点集子集
  • 放弃手写代码吧!用低代码你能生成各种源码

    很多同学不知道为什么要用Low code做开发 传统IT开发不行么 当然可以 传统IT自研软件开发 通过编程去写代码 还有数据库 API 第三方基础架构等 这个方式很好 但不可避免的会带来开发周期长 难度大 技术人员不易开发维护 因此价格及
  • EDUCODER---WEB__JavaScript学习手册十:正则表达式

    第一关 字符串字面量 请在此处编写代码 Begin var pattern js n End 第二关 字符类 请在此处编写代码 Begin var pattern1 a zA Z 0 9 var pattern2 A 0 9 End 第三关
  • Linux下Python环境安装与部署

    因为我是Python零基础 所以如何部署全靠百度 这边我把我查到的资料和安装使用过程中遇到写下来 如果有写的不对的或者有更好的方式 欢迎评论指出 一 Python环境安装 网上有很多安装教程 可以自行百度安装 我参考的是这个 仅第一步安装p
  • The Lost House【树形DP+期望+构造路径】

    题目链接 POJ 2057 题意 有一棵N的点的树 开始的时候蜗牛在1号结点 它不知道它的家在哪个叶子结点 树上的有些结点有虫虫 虫虫会告诉你 你的家是否在以它所在结点为根的子树上 现在需要你规划走的方案 使得找到哪个叶子结点才是家的所走路
  • python将word表格转写入excel

    Notes 想将一份 word 文件中的几个表格转写入 excel 文件中 后续用 excel 处理 用到 python docx 和 pandas 分别处理 word 和 excel 安装 python docx pip install
  • pytorch中网络loss传播和参数更新理解

    相比于2018年 在ICLR2019提交论文中 提及不同框架的论文数量发生了极大变化 网友发现 提及tensorflow的论文数量从2018年的228篇略微提升到了266篇 keras从42提升到56 但是pytorch的数量从87篇提升到
  • 利用R包ggmap进行空间可视化

    ggmap 是在R环境里调用地图作用可视化的利器 它的语法结构跟ggplot2非常相似 也使R语言的用户可以迅速上手 ggmap 结合 ggplot 可以方便快速绘制基于地图的可视化图表 下面的文章里 我将用两个例子 三藩市的犯罪记录 和
  • require 方法详解

    在 NodeJS 中有一个方法是我们使用频率最高的 那就是 require 方法 NodeJs 遵循 CommonJS 规范 该规范的核心是通过 require来加载其他依赖的模块 几个问题 module exports 或者 export