require用法及源码解析
一.require()的基本用法
require语句内部逻辑:
当 Node 遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require('http'))
a. 返回该模块。
b. 不再继续执行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
- X
- X.js
- X.json
- X.node
c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
- X/package.json(main字段)
- X/index.js
- X/index.json
- X/index.node
(3)如果 X 不带路径
a. 根据 X 所在的父模块,确定 X 可能的安装目录。
b. 依次在每个目录中,将 X 当成文件名或目录名加载。(4) 抛出 "not found"
请看一个例子。
当前脚本文件 /home/ry/projects/foo.js 执行了 require('bar') ,这属于上面的第三种情况。Node 内部运行过程如下。
首先,确定 x 的绝对路径可能是下面这些位置,依次搜索每一个目录。
/home/ry/projects/node_modules/bar /home/ry/node_modules/bar /home/node_modules/bar /node_modules/bar
搜索时,Node 先将 bar 当成文件名,依次尝试加载下面这些文件,只要有一个成功就返回。
bar bar.js bar.json bar.node
如果都不成功,说明 bar 可能是目录名,于是依次尝试加载下面这些文件。
bar/package.json(main字段) bar/index.js bar/index.json bar/index.node
如果在所有目录中,都无法找到 bar 对应的文件或目录,就抛出一个错误。
二.Module构造函数
源码:
require的源码早Node的lib/module.js文件.下面是简化过的:
![](/assets/blank.gif)
![](/assets/blank.gif)
1 function Module(id, parent) { 2 this.id = id; 3 this.exports = {}; 4 this.parent = parent; 5 this.filename = null; 6 this.loaded = false; 7 this.children = []; 8 } 9 10 module.exports = Module; 11 12 var module = new Module(filename, parent);
View Code
上面代码中,Node定义了一个构造函数Module,所有的模块都是Module的实例.可以看到,当前模块(module.js)也是Module的一个实例.
每个实例都要自己的属性.新建一个脚本看看这些属性的值是什么.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 // a.js 2 3 console.log('module.id: ', module.id); 4 console.log('module.exports: ', module.exports); 5 console.log('module.parent: ', module.parent); 6 console.log('module.filename: ', module.filename); 7 console.log('module.loaded: ', module.loaded); 8 console.log('module.children: ', module.children); 9 console.log('module.paths: ', module.paths);
View Code
运行这个脚本:
![](/assets/blank.gif)
![](/assets/blank.gif)
1 $ node a.js 2 3 module.id: . 4 module.exports: {} 5 module.parent: null 6 module.filename: /home/ruanyf/tmp/a.js 7 module.loaded: false 8 module.children: [] 9 module.paths: [ '/home/ruanyf/tmp/node_modules', 10 '/home/ruanyf/node_modules', 11 '/home/node_modules', 12 '/node_modules' ]
View Code
可以看到,如果没有父模块,直接调用当前模块,parent属性就是null,id属性就是一个点.filename属性是模块的绝对路径,path属性是一个数组,包含模块肯的位置
另外,输出这些内容时,模块还没有全部加载,所以loaded属性为false.
新建另一个脚本文件b.js让其调用a.js
![](/assets/blank.gif)
![](/assets/blank.gif)
1 // b.js 2 3 var a = require('./a.js'); 4 5 //运行b.js 6 7 $ node b.js 8 9 module.id: /home/ruanyf/tmp/a.js 10 module.exports: {} 11 module.parent: { object } 12 module.filename: /home/ruanyf/tmp/a.js 13 module.loaded: false 14 module.children: [] 15 module.paths: [ '/home/ruanyf/tmp/node_modules', 16 '/home/ruanyf/node_modules', 17 '/home/node_modules', 18 '/node_modules' ]
View Code
上面代码中,由于a.js被b.js调用,所以parent属性指向b.js模块,id属性和filename属性一直,都是模块的绝对路径.
三.模块实例的require方法
每个模块实例都有一个require方法.
Module.prototype.require =funtion (path){
return Module._load(path,this);
};
由此可知,require并不是全局命令,而是每个模块提供的一个内部方法,也就是说,只有在模块内部才能使用requrie命令(额日益例外是REPL环境).
另外,requrie其实内部调用Module._load方法.
下面来看Module._load的源码.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module._load = function(request, parent, isMain) { 2 3 // 计算绝对路径 4 var filename = Module._resolveFilename(request, parent); 5 6 // 第一步:如果有缓存,取出缓存 7 var cachedModule = Module._cache[filename]; 8 if (cachedModule) { 9 return cachedModule.exports; 10 11 // 第二步:是否为内置模块 12 if (NativeModule.exists(filename)) { 13 return NativeModule.require(filename); 14 } 15 16 // 第三步:生成模块实例,存入缓存 17 var module = new Module(filename, parent); 18 Module._cache[filename] = module; 19 20 // 第四步:加载模块 21 try { 22 module.load(filename); 23 hadException = false; 24 } finally { 25 if (hadException) { 26 delete Module._cache[filename]; 27 } 28 } 29 30 // 第五步:输出模块的exports属性 31 return module.exports; 32 };
View Code
上面代码中,首先解析出模块的绝对路径(filename),以作为模块的识别符.然后,如果模块已经在缓存中,就从缓存取出;如果不在缓存中,就加载模块.
因此,Module._load的关键步骤是两个.
Module._resovleFilename():确定模块的绝对路径
module.load():加载模块
四.模块的绝对路径
下面是Module._resolveFilename方法的源码.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module._resolveFilename = function(request, parent) { 2 3 // 第一步:如果是内置模块,不含路径返回 4 if (NativeModule.exists(request)) { 5 return request; 6 } 7 8 // 第二步:确定所有可能的路径 9 var resolvedModule = Module._resolveLookupPaths(request, parent); 10 var id = resolvedModule[0]; 11 var paths = resolvedModule[1]; 12 13 // 第三步:确定哪一个路径为真 14 var filename = Module._findPath(request, paths); 15 if (!filename) { 16 var err = new Error("Cannot find module '" + request + "'"); 17 err.code = 'MODULE_NOT_FOUND'; 18 throw err; 19 } 20 return filename; 21 };
View Code
上面代码中.在Module.resolveFilename方法内部,又调用了两个方法Module.resolveLookupPaths()和Module._findPath(),前者用来列出可能的路径,猴子用来确认
哪一个路径为真.
Module._resolveLookupPaths()的运行结果.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 [ '/home/ruanyf/tmp/node_modules', 2 '/home/ruanyf/node_modules', 3 '/home/node_modules', 4 '/node_modules' 5 '/home/ruanyf/.node_modules', 6 '/home/ruanyf/.node_libraries', 7 '$Prefix/lib/node' ]
View Code
上面的模块就是模块所有可能的路径.基本是是从当前路径上一级向上寻找node_modules子目录.最后那三个路径,主要是为了历史原因保持兼容,实际上已经很少用了.
有了可能的路径以后,下面就是Module._findPath()的源码,用来确定到底哪一个是正确路径.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module._findPath = function(request, paths) { 2 3 // 列出所有可能的后缀名:.js,.json, .node 4 var exts = Object.keys(Module._extensions); 5 6 // 如果是绝对路径,就不再搜索 7 if (request.charAt(0) === '/') { 8 paths = ['']; 9 } 10 11 // 是否有后缀的目录斜杠 12 var trailingSlash = (request.slice(-1) === '/'); 13 14 // 第一步:如果当前路径已在缓存中,就直接返回缓存 15 var cacheKey = JSON.stringify({request: request, paths: paths}); 16 if (Module._pathCache[cacheKey]) { 17 return Module._pathCache[cacheKey]; 18 } 19 20 // 第二步:依次遍历所有路径 21 for (var i = 0, PL = paths.length; i < PL; i++) { 22 var basePath = path.resolve(paths[i], request); 23 var filename; 24 25 if (!trailingSlash) { 26 // 第三步:是否存在该模块文件 27 filename = tryFile(basePath); 28 29 if (!filename && !trailingSlash) { 30 // 第四步:该模块文件加上后缀名,是否存在 31 filename = tryExtensions(basePath, exts); 32 } 33 } 34 35 // 第五步:目录中是否存在 package.json 36 if (!filename) { 37 filename = tryPackage(basePath, exts); 38 } 39 40 if (!filename) { 41 // 第六步:是否存在目录名 + index + 后缀名 42 filename = tryExtensions(path.resolve(basePath, 'index'), exts); 43 } 44 45 // 第七步:将找到的文件路径存入返回缓存,然后返回 46 if (filename) { 47 Module._pathCache[cacheKey] = filename; 48 return filename; 49 } 50 } 51 52 // 第八步:没有找到文件,返回false 53 return false; 54 };
View Code
经过上面代码,就可以找到模块的绝对路径了.
有时在项目代码中,需要调用模块的绝对路径,那么除了module.filename,Node还提供一个require.resolve方法,供外部调用,用于从模块名渠道绝对路径.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 require.resolve = function(request) { 2 return Module._resolveFilename(request, self); 3 }; 4 5 // 用法 6 require.resolve('a.js') 7 // 返回 /home/ruanyf/tmp/a.js
View Code
五.加载模块
有了模块的绝对路径,就可以加载该模块了.下面是module.load方法的源码.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module.prototype.load = function(filename) { 2 var extension = path.extname(filename) || '.js'; 3 if (!Module._extensions[extension]) extension = '.js'; 4 Module._extensions[extension](this, filename); 5 this.loaded = true; 6 };
View Code
上面代码中,首先确定模块的后缀名,不同的后缀名对应不同的加载方法.下面是.js和.json后缀名对应的处理方法.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module._extensions['.js'] = function(module, filename) { 2 var content = fs.readFileSync(filename, 'utf8'); 3 module._compile(stripBOM(content), filename); 4 }; 5 6 Module._extensions['.json'] = function(module, filename) { 7 var content = fs.readFileSync(filename, 'utf8'); 8 try { 9 module.exports = JSON.parse(stripBOM(content)); 10 } catch (err) { 11 err.message = filename + ': ' + err.message; 12 throw err; 13 } 14 };
View Code
这里只讨论js文件的加载.首先,将模块文件读取成字符串,然后剥离utf8编码特有的BOM文件头,最后编译该模块.
module._compile方法用于模块的编译.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 Module.prototype._compile = function(content, filename) { 2 var self = this; 3 var args = [self.exports, require, self, filename, dirname]; 4 return compiledWrapper.apply(self.exports, args); 5 };
View Code
上面的代码等同于下面的形式.
![](/assets/blank.gif)
![](/assets/blank.gif)
1 (function (exports, require, module, __filename, __dirname) { 2 // 模块源码 3 });
View Code
也就是说,模块的加载实质上就是注入exports,requrie,module三个全局变量,然后执行的源码,然后将模块的export编码的值输出.
原文地址:http://www.ruanyifeng.com/blog/2015/05/require.html
转载于:https://www.cnblogs.com/ZSG-DoBestMe/p/5291731.html
require用法及源码解析相关推荐
- DeepLink用法及源码解析
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 1. 简介 DeepLink官网上有这样的解释: When a ...
- MultiDex 不得不说的用法与源码解析
前言 开发Android应用或者SDK方向小伙伴们,经过多版本的迭代,新功能的不断增加,依赖多个开源项目,使用第三方SDK,都会导致Apk大小急速膨胀.最终会导致方法超限这一问题,下面让大家了解下 ...
- 什么是LinkedList?什么时候使用它呢?Java LinkedList结构、用法及源码解析
前言:我们学习java时都知道ArrayList实现List接口,LinkedList也实现List接口,但我们平时用的时候LinkedList却很少被用到.那么,LinkedList什么时候该用到呢 ...
- Handler全家桶之 —— Handler 源码解析
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本文首发于本人简书 前言 好记性不如烂笔头. 这是一个系列文章,将会包括: Handler全家桶之 -- Handler 源码解析 ...
- php 框架源码分析,Laravel框架源码解析之模型Model原理与用法解析
本文实例讲述了Laravel框架源码解析之模型Model原理与用法.分享给大家供大家参考,具体如下: 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看着你们. 根据单一责任开发原则来讲, ...
- PrepareStatement用法(附源码解析)
PrepareStatement 基本用法 1. 加载驱动 首先在pom.xml 中引入 mysql 依赖 <dependency><groupId>mysql</gro ...
- Android Glide图片加载框架(二)源码解析之into()
文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...
- axios网络请求框架源码解析
早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持.并且做了对请求参数拼接.Json对象序列化等基本功能. 到0.19.0版本时,内部请求已经变为了在Node ...
- [源码解析] PyTorch 流水线并行实现 (6)--并行计算
[源码解析] PyTorch 流水线并行实现 (6)–并行计算 文章目录 [源码解析] PyTorch 流水线并行实现 (6)--并行计算 0x00 摘要 0x01 总体架构 1.1 使用 1.2 前 ...
最新文章
- Koa2和Redux中间件源码研究
- 操作系统CnetOS_7—systemd管理实践指南
- java环境变量_java环境变量
- 计算机组成原理:总线
- shiro进行散列算法操作
- zip,tar,打包并压缩
- mysql 分钟_mysql分钟到小时和分钟
- C语言定义一个头节点,一个关于C语言链表头结点的问题
- 知识图谱应用合辑第一篇:多模态知识图谱的使用领域及最新进展
- 李宏毅机器学习(十一)meta-learning和ML一样是三个步骤
- 《软件项目管理(第二版)》期末考试试题总结/复习资料
- toj 4614 Infinite Dictionaries
- 精通Android自定义View(十三)事件分发简述
- ug快捷键命令大全_UG命令大全及快捷键的用法用处说明
- QT控件出现乱码问题
- 【实习之T100开发】T100程序功能代码积累
- 所谓数据结构,对python来说不过如此
- HDU5855(最大权闭合图构图技巧+裸的最大流)
- JAVA之基数排序LSD顺序
- 【解决】无法为JSP编译类: Enumeration cannot be resolved to a variable
热门文章
- 给定0-1矩阵求连通域
- 【论文笔记+代码解读】《ATTENTION, LEARN TO SOLVE ROUTING PROBLEMS!》
- FusionCharts Suite XT CRACK,全面的图表解决方案库
- GXT 3.1.1 的VBoxLayoutContainer 高度BUG
- 用差分方程写三角形的重心
- python实现dns劫持_DNS劫持原理与实现
- PG系统表之pg_class
- 我们的管理:战略管理
- 黄聪:wordpress调用函数大全
- el-form中el-form-item label换行