require 函数加载模块原理(被加载的模块会先执行一次)|学习笔记

简介: 快速学习 require 函数加载模块原理(被加载的模块会先执行一次)

开发者学堂课程【Node.js 入门与实战:require函数加载模块原理(被加载的模块会先执行一次)】学习笔记,与课程紧密联系,让用户快速学习知识

课程地址:https://developer.aliyun.com/learning/course/588/detail/8297


require 函数加载模块原理(被加载的模块会先执行一次)

 

目录:

一、require 加载模块的作用

二、require 内部执行机制

 

一、require 加载模块的作用

新建文件:a.js,b.js,c.js

一个js文件就是一个模块

//在a.js模块中加载b.js模块

//因为a.js与b.js模块在同一个文件夹,可以直接./b.js

Var b=require(‘./b.js’)

//在b.js模块中添加内容

Function add(x,y){

Return x+y;

}

Var result=add(100,1000)

Console.log(result)

启动运行 a.js

先进入js当前文件夹

控制台输入:node a.js

控制台输出:1100

取消 a.js中的声明变量

Require(‘./b.js’)

重新运行结果与上文一样为1100

执行 reuqire 加载模块时会执行模块所有代码,被加载模块代码全部执行一次

多次在 a.js 模块中加载 b.js 模块

Require(‘./b.js’)

Require(‘./b.js’)

Require(‘./b.js’)

Require(‘./b.js’)

Require(‘./b.js’)

Require(‘./b.js’)

Require(‘./b.js’)

再次控制台运行:

只会打印一行1100

原因在于require加载模块无论文件模块还是核心模块还是第三方模块,会缓存起来,加载直接去缓存中获取,不会重新加载

 image.png

二、require 内部执行机制

从源代码文件中找出 require 函数源码

首先清楚 require,模块是谁的,每一个模块加载时都会被包装成 module

Nodejs.org 官网中的 modules 模块图示:

image.png

任何一个模块加载都会封装为 modules,表示当前模块自身

a. js 中添加代码:

//查找路径

Console.log(module.paths)

代码运行结果:

image.png

逐层查找模块

Require 函数属于 modules 模块中

查看 Module.js 源文件:

Module 内置函数

function -Module(id , -parent)

{this.id=-id;

this.exports=·;this.parent·=-parent;

if-(parent·&&-parent.children){

parent.children.push(this);

this.filename - =-null;

this.loaded-=.false;this.children·--[];

moduLe.exports= Module;

Require 函数源码:

ModuLe.prototype.require-function(path) {assertlpath,"missing path ');

assert(typeof path --- 'string ' , "path must be a string" );return Module._load(path,this,/* isMain*/ false);

};

Module._load = function(request,parent,isMain){

if (parent)·i

debug( 'Module._load ·REQUEST- %s parent: ·%s ' , request, parent.id);.}

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

var cachedModule = Module._cache[filename];

//判断缓存,如果有则拿出缓存

if (cachedModule) {

return cachedModule.exports;}

if (NativeModule.nonInternalExists(filename)){debug( 'load native module %s ' , request);

return NativeModule.require(filename);}

var module = new Module(filename,parent);

if (isMain){

process.mainModule = module;module.id - '.';

}

//缓存的键就是路径,若是模块名称这就是模块名称

ModuLe._cache[filename] - module;

//调用try方法加载模块

tryModuleLoad(module,filename);return moduLe.exports;

};

源代码解读:

// 1. module.js

//Loads a module at the given file path. Returns that module 'sl/^exports` property.

//加载给定的模块,并返回该模块中 module.exports中的值

//之所以在每个模块中都能使用require()函数是因为 require函数定义在了每个模块中

ModuLe.prototype.require - function(path){

assert(path,'missing path ');

assert(typeof path --= 'string" , "path must be a string" );

return MoHule._load(path,this,/* isMain*/ false);

};

_load 方法传入模块名称和模块类型

该方法做了5件事:

1.检查 Module._cache 中是否有缓存的模块实例

2.如果缓存中没有,那么创建一个 Moudle 实例

3.将创建的 Module 实例保存到缓存中,供下次使用。

4.调用 module.load()读取模块内容,然后调用 module. compile()编译执行-如果加载解析出错,那么从缓存中删除该模块

5.返回 module.exports

tryModuleLoad方法

// 3.module.js

function tryModuleLoad (module,filename){var threw = true;

try {

//读取 b.js 源文件,读取进来封装和编译执行

module. load(filename);threw - false;

}finally {

if (threw) i

delete ModuLe._cache[filename];

}

}

}

Load方法

//4. module.js

// Given a file name,pass it to the proper extension handler.

//该模块中对要加载的模块进行编译

ModuLe.prototype.load = function(fiLename) {

debug( " load jfor module j',filename,this.id);

assert( !this.loaded);

this.filename = filename;

this.paths = Module._nodeModulePaths(path.dirname(filename));

var extension = path.extname(filename)ll '.js ';

if (!Module._extensions[extension]) extension - '.js ';

ModuLe._extensions[extension](this, ilename);

//此行代码对被加载的模块实现了编译 this.loaded = true;

};

Extension 函数编译模块,需要读取js文件内容

//5. module.js(编译)

//Native extension for .js

Module._extensions[ '.js']- function(module, filename){

//说明 require()加载模块是同步执行的

//同步读取而并非异步

var content = fs.reaTFileSync(filename, : "utf8');

module._compile(internalModule.stripBOM(content), filename);

};

异步读取回到函数:readFile 方法

同步读取:返回值就是文件读取到的结果:readFileSync

编译完毕后执行结果,把当前模块返回出去

Var b=require(‘./b.js’)

最终拿到返回结果,表示在任何函数中都可使用require模块,缓存加载路径命名为键,根据文件路径读取文件,同步读取文件。

相关文章
|
8月前
|
存储 人工智能 监控
OpenClaw到底是什么?普通人能用它干嘛?
OpenClaw是一款开源AI智能体,以红色龙虾为标识,主打“真正能做事”——理解指令、自主拆解任务、调用软件执行。支持文件整理、邮件处理、报告生成、日程管理、抢购监控、夜间爬取等六大实用场景,可本地或云端部署,兼顾高效与隐私安全。
6535 5
|
JavaScript API
深入探索fs.WriteStream:Node.js文件写入流的全面解析
深入探索fs.WriteStream:Node.js文件写入流的全面解析
|
10月前
|
设计模式 人工智能 程序员
【架构演进】智能体来了(西南总部)深度解析:多智能体协作(Multi-Agent)系统的设计哲学与工程实践
本文探讨AI架构从单体大模型向多智能体系统(MAS)的范式跃迁,基于“智能体来了(西南总部)”技术研判,解析角色解耦、消息路由与共享记忆等核心设计,揭示如何构建高效协作的“数字蜂群”,推动分布式AI从理论走向工程化落地。
924 2
|
Linux iOS开发 MacOS
Flutter桌面开发之窗口管理
今天跟着大家一起学习桌面开发之窗口管理,关于桌面窗口管理,我网上查了很久资料,我觉得window_manager 这个插件几乎可以满足市面上大部分需求了
Flutter桌面开发之窗口管理
|
12月前
|
缓存 JSON 安全
Flutter 网络请求深度解析
本文深入探讨Flutter网络请求架构,对比http、dio、retrofit等库,重点详解Dio封装、拦截器、统一响应、Repository模式,并结合Riverpod状态管理与JSON序列化,构建高性能、可维护的网络层。
873 3
|
JSON iOS开发 数据格式
最新研发flutter3.32+window_manager客户端OS管理系统
原创Flutter3.32+Dart3.8+Getx+Window_Manager实战桌面客户端os系统解决方案。支持macOS和windows两种主题风格、自定义桌面栅格布局。
910 50
|
存储 缓存 JavaScript
Flutter 学习之封装 WebView
【10月更文挑战第24天】通过以上的探讨,我们可以看出,在 Flutter 中封装 WebView 是非常有必要的,它可以提高代码的复用性、增强可维护性、提供统一接口。在实际应用中,我们需要根据具体的需求和场景,选择合适的封装方法和技术,以实现更好的效果。
|
负载均衡 安全 网络安全
什么是 Traceroute?为什么要使用它?
【8月更文挑战第31天】
1975 1
|
监控 网络协议 Linux
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?