在 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;

  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方法的返回值

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

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 查找模块的流程如下:

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

本文完

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

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

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

require 方法详解相关推荐

  1. php node 目录,node.js基于fs模块对系统文件及目录进行读写操作的方法详解

    本文主要介绍了node.js基于fs模块对系统文件及目录进行读写操作的方法,结合实例形式分析了nodejs使用fs模块针对文件与目录的读写.创建.删除等相关操作技巧,需要的朋友可以参考下. 如果要用这 ...

  2. node作为java中间间_node作为中间服务层如何发送请求(发送请求的实现方法详解)...

    GET请求: var http = require('http'); var qs = require('querystring'); var data = { a: 123, time: new D ...

  3. php 2003生成word,使用PHPWord生成word文档的方法详解

    使用PHPWord生成word文档的方法详解 来源:中文源码网    浏览: 次    日期:2019年11月5日 [下载文档:  使用PHPWord生成word文档的方法详解.txt ] (友情提示 ...

  4. 删除oracle数据库的三种方法,oracle数据库的删除方法详解

    oracle数据库的删除方法详解 1.图形界面删除 练习之前记得创建快照 执行命令之前要保证数据库属于open状态 SQL> alter database open; [oracle@local ...

  5. 下拉多选框 微信小程序_微信小程序下拉框组件使用方法详解

    本文实例为大家分享了微信小程序下拉框组件的使用方法,供大家参考,具体内容如下 适用场景 1.省市三级联动 2.出生日期选择 3.性别选择 4.一般性的下拉选择等 一.省市三级联动使用 注意mode = ...

  6. PHP之十六个魔术方法详解 转自:青叶

    目录 PHP之十六个魔术方法详解 前言 范例 〇.__serialize() 和 __unserialize() 一. __construct(),类的构造函数 二.__destruct(),类的析构 ...

  7. php require的用法,php require用法详解

    php require是php的内置函数,作用是引入或者包含外部php文件,工作原理是当本身php文件被执行时,则外部文件的内容就将被包含进该自身php文件中:当包含的外部文件发生错误时,系统将抛出错 ...

  8. python统计csv行数_对Python 多线程统计所有csv文件的行数方法详解

    如下所示: #统计某文件夹下的所有csv文件的行数(多线程) import threading import csv import os class MyThreadLine(threading.Th ...

  9. python修改文件内容_Python批量修改文本文件内容的方法详解

    这篇文章主要介绍了Python批量修改文本文件内容的方法的相关资料,需要的朋友可以参考下 Python批量替换文件内容,支持嵌套文件夹 import os path="./" fo ...

最新文章

  1. 语义分割--Global Deconvolutional Networks for Semantic Segmentation
  2. 【正一专栏】走过2017——坚持
  3. 图论--网络流--最大流 洛谷P4722(hlpp)
  4. 激光slam_机器人主流定位技术,激光SLAM与视觉SLAM谁更胜一筹
  5. JS的eval函数解密反混淆
  6. Linux写出相应密码的用途,linux运维面试题中级
  7. zplane函数怎么用m文件调用_elastique.dll,下载,简介,描述,修复,等相关问题一站搞定_DLL之家...
  8. 字体在ppt中可以整体替换吗_制作PPT必备的6个技巧,个个让人相见恨晚!你确定不来学一学?...
  9. UCD的产品设计原则
  10. matlab下载安装全教程
  11. python怎么读单词和古文,用文言文写Python
  12. 为什么老实人很难当上领导?因为他们身上有这个致命弱点
  13. 从Q_Learning看强化学习
  14. matlab 实时信号平滑,信号平滑处理 - MATLAB Simulink Example - MathWorks 中国
  15. PHP解密小程序加密信息
  16. mybatis中获取当前时间_MySQL NOW和SYSDATE函数:获取当前时间日期
  17. 使用hexo+icarus快速搭建属于自己的博客网站
  18. 基于深度学习的自动车牌识别(详细步骤+源码)
  19. C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示
  20. 深度学习 warmup 策略

热门文章

  1. 【从Ubuntu16.04升级Ubuntu18.04,并安装ROS系统】熟能生巧,避坑专用
  2. vs2010修改生成的exe名字
  3. 【工具】开发工具记录(CoolFormat源代码格式化工具)
  4. 西部数据移动硬盘无法访问 参数错误 修复办法(官方)
  5. 一起认识关于CQC认证详解!!!
  6. 适合同学聚会时玩的19个小游戏
  7. 让前端飞:形象理解 Hbuilder、文本标记、超链接、列表、图片、表格
  8. 河北一服装厂车间屋顶坍塌 20余人被埋有伤亡
  9. C#实现登录界面,密码星号显示(隐藏输入密码)
  10. c# mysql oledb_C#如何连接数据库?OleDbConnection与SqlConnection的区别