一.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文件.下面是简化过的:

  

 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的一个实例.

   每个实例都要自己的属性.新建一个脚本看看这些属性的值是什么.

   

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

  运行这个脚本:

  

 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

  

 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的源码.

  

 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方法的源码.

  

 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()的运行结果.

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()的源码,用来确定到底哪一个是正确路径.

 

 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方法,供外部调用,用于从模块名渠道绝对路径.

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方法的源码.

  

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后缀名对应的处理方法.

  

 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方法用于模块的编译.

  

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

  上面的代码等同于下面的形式.

  

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用法及源码解析相关推荐

  1. DeepLink用法及源码解析

    终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 1. 简介 DeepLink官网上有这样的解释: When a ...

  2. MultiDex 不得不说的用法与源码解析

    前言   开发Android应用或者SDK方向小伙伴们,经过多版本的迭代,新功能的不断增加,依赖多个开源项目,使用第三方SDK,都会导致Apk大小急速膨胀.最终会导致方法超限这一问题,下面让大家了解下 ...

  3. 什么是LinkedList?什么时候使用它呢?Java LinkedList结构、用法及源码解析

    前言:我们学习java时都知道ArrayList实现List接口,LinkedList也实现List接口,但我们平时用的时候LinkedList却很少被用到.那么,LinkedList什么时候该用到呢 ...

  4. Handler全家桶之 —— Handler 源码解析

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本文首发于本人简书 前言 好记性不如烂笔头. 这是一个系列文章,将会包括: Handler全家桶之 -- Handler 源码解析 ...

  5. php 框架源码分析,Laravel框架源码解析之模型Model原理与用法解析

    本文实例讲述了Laravel框架源码解析之模型Model原理与用法.分享给大家供大家参考,具体如下: 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看着你们. 根据单一责任开发原则来讲, ...

  6. PrepareStatement用法(附源码解析)

    PrepareStatement 基本用法 1. 加载驱动 首先在pom.xml 中引入 mysql 依赖 <dependency><groupId>mysql</gro ...

  7. Android Glide图片加载框架(二)源码解析之into()

    文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...

  8. axios网络请求框架源码解析

    早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持.并且做了对请求参数拼接.Json对象序列化等基本功能. 到0.19.0版本时,内部请求已经变为了在Node ...

  9. [源码解析] PyTorch 流水线并行实现 (6)--并行计算

    [源码解析] PyTorch 流水线并行实现 (6)–并行计算 文章目录 [源码解析] PyTorch 流水线并行实现 (6)--并行计算 0x00 摘要 0x01 总体架构 1.1 使用 1.2 前 ...

最新文章

  1. Koa2和Redux中间件源码研究
  2. 操作系统CnetOS_7—systemd管理实践指南
  3. java环境变量_java环境变量
  4. 计算机组成原理:总线
  5. shiro进行散列算法操作
  6. zip,tar,打包并压缩
  7. mysql 分钟_mysql分钟到小时和分钟
  8. C语言定义一个头节点,一个关于C语言链表头结点的问题
  9. 知识图谱应用合辑第一篇:多模态知识图谱的使用领域及最新进展
  10. 李宏毅机器学习(十一)meta-learning和ML一样是三个步骤
  11. 《软件项目管理(第二版)》期末考试试题总结/复习资料
  12. toj 4614 Infinite Dictionaries
  13. 精通Android自定义View(十三)事件分发简述
  14. ug快捷键命令大全_UG命令大全及快捷键的用法用处说明
  15. QT控件出现乱码问题
  16. 【实习之T100开发】T100程序功能代码积累
  17. 所谓数据结构,对python来说不过如此
  18. HDU5855(最大权闭合图构图技巧+裸的最大流)
  19. JAVA之基数排序LSD顺序
  20. 【解决】无法为JSP编译类: Enumeration cannot be resolved to a variable

热门文章

  1. 给定0-1矩阵求连通域
  2. 【论文笔记+代码解读】《ATTENTION, LEARN TO SOLVE ROUTING PROBLEMS!》
  3. FusionCharts Suite XT CRACK,全面的图表解决方案库
  4. GXT 3.1.1 的VBoxLayoutContainer 高度BUG
  5. 用差分方程写三角形的重心
  6. python实现dns劫持_DNS劫持原理与实现
  7. PG系统表之pg_class
  8. 我们的管理:战略管理
  9. 黄聪:wordpress调用函数大全
  10. el-form中el-form-item label换行