require() 方法详解

您所在的位置:网站首页 raptor怎么调用 require() 方法详解

require() 方法详解

2023-09-27 21:35| 来源: 网络整理| 查看: 265

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

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

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

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

Node 模块的分类 build-in modules —— Nodejs 中以 C++ 形式提供的模块。 constant module —— Nodejs 中定义常量的模块。 native module —— Nodejs 中以 javascript 形式提供的模块。 第三方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); } exports.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 实例,包括入口文件。

完整步骤:

调用父模块的 require 方法(父模块是指调用模块的当前模块)

require = function require(path) { return mod.require(path); };

调用 Module 的 _load 方法

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

const filename = Module._resolveFilename(request, parent, isMain);

根据 fileName 判断是否存在该模块的缓存

如果存在缓存,则调用 updateChildren 方法在更新缓存内容,并返回缓存 如果不存在缓存,则继续执行

当做原生模块,调用 loadNativeModule 方法进行加载

如果加载成功,则返回该原生模块 否则,继续执行

根据当前模块名(路径)和父模块对象生成一个 Module 实例:

const module = cachedModule || new Module(filename, parent);

再判断该模块是否是入口文件

if (isMain) { process.mainModule = module; module.id = '.'; }

将该模块的实例存入到 Module 的缓存中

Module._cache[filename] = module;

image

该模块的实例调用自身的 load 方法,根据 fileName 加载模块

module.load(filename);

获取该模块文件的后缀名称

const extension = findLongestRegisteredExtension(filename);

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

根据后缀名称解析模块文件内容

Module._extensions[extension](this, filename);

根据fileName读取文件内容

content = fs.readFileSync(filename, 'utf8');

编译并执行读取到的文件,调用 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)); }, });

修改该模块的加载状态为true

this.loaded = true;

加载成功。

总结

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

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

image

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

image

本文完

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

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

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3