开发者学堂课程【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加载模块无论文件模块还是核心模块还是第三方模块,会缓存起来,加载直接去缓存中获取,不会重新加载
二、require 内部执行机制
从源代码文件中找出 require 函数源码
首先清楚 require,模块是谁的,每一个模块加载时都会被包装成 module
Nodejs.org 官网中的 modules 模块图示:
任何一个模块加载都会封装为 modules,表示当前模块自身
a. js 中添加代码:
//查找路径
Console.log(module.paths)
代码运行结果:
逐层查找模块
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.
lo
ad(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模块,缓存加载路径命名为键,根据文件路径读取文件,同步读取文件。