Node.js模块:require实现原理、 exports与m
参考文献: module-模块(来自官方文档)
1. exports
与module.exports
的区别
分析一下node.js中require方法的实现原理,一切就都明白了!(所以可以跳过第一步,直接看require实现原理)
我们先将需要到的元素准备好,假设我们有两个文件:
一个是我们需要导入的模块文件,比如起名叫my-module-file.js
另一个是需要导入my-module-file.js
的文件,比如起名叫index.js
这两个文件的代码比如如下所示:
/**
* my-module-file.js
*/
var one = 1;
var two = 2;
exports.a = one;
module.exports.b = two;
/**
* index.js
*/
var MyModule = require('./my-module-file.js'); // 其实 return module.exports
/*
* 输出为1
* why?上面不刚说了 require 是 return module.exports吗?
* 按道理讲不应该输出 undefined 吗?module.exports 中不是没有属性 a 吗?
* 这是因为在node.js的模块系统中,初始状态是 exports = module.exports = {}
* 所以他们共同指向同一个对象,肯定有属性 a
*/
console.log(MyModule.a);
console.log(MyModule.b); // 输出为2
改下一下再看看👀效果
/**
* my-module-file.js
*/
var one = 1;
var two = 2;
exports = { a: one };
module.exports.b = two;
/**
* index.js
*/
var MyModule = require('./my-module-file.js'); // 其实 return module.exports
/*
* 输出为 undefined
* why?怎么不是输出为 1 了?
* 这是因为 exports = module.exports = {} 这个初始状态被 exports = { a: one } 这个赋值破坏了
* 这时他们已经不再指向同一个对象了
* exports指向的是 { a: one } 这个对象,
* 而 module.exports 还是指向初始状态的那个对象,不过因为添加了属性 b,所以这个对象的看起来是 { b: two }
*/
console.log(MyModule.a);
console.log(MyModule.b); // 输出为2
好了,先对上面说的一大堆来个小结:
- 本质原因:
exports = module.exports = {}
-
require
方法return
的是module.exports
- 为什么要设计个
exports
?简化类似module.exports.b = two
这种代码,你就可以直接写成exports.b = two
了。当然,前提是不要改变初始时的对象指向,比如exports = { a: one }
就是改变初始时的对象指向了
2. require
实现原理:
有没有想过require
方法是怎样导出模块的?
require
的方法实现应该看起来像是这样的:
function require(your-module){
var module = {
exports: {}
};
var exports = module.exports;
(function(exports, module) {
/**
* 将 your-module 里你写的代码放在了这个立即执行的函数里
* node.js是怎么将你的代码放过来的呢,应该是通过文件读取操作,将文件内容写入进来的
* 具体文件读取操作是怎么实现的,不在本文讨论
* 那接下来我们将 your-module 里的代码搬过来看看是什么样子的呗
* 那我们就把 my-module-file.js 里的代码搬过来呗
*/
var one = 1; // 知道为啥在模块文件(my-module-file.js)中定义的变量仅限在模块文件中访问了吧
var two = 2; // 因为他们都是在“立即执行函数”里执行的,外面肯定访问不到
exports.a = one;
module.exports.b = two;
})(exports, module);
return module.exports;
}
看完上面这段代码,是不是就很清晰了。
3. 模块查找机制
require(your-module)
-
如果
your-module
是以./ 或 ../
开头的,那就以当前文件所在目录的相对地址去查找模块的,以/
开头就是以根目录去查找模块呗(好像没见用过/
) -
如果不是以上面的这些情况开头的,就是去查找
node
相关的模块了,怎么说?
① 首先是查找node.js
的核心模块
,啥叫核心模块
?就是编译进node
命令里面的模块,比如http
模块
② 第二步就是查找node_modules
里面的模块了,但 首先会先去缓存
里查找模块。那node
是怎么匹配缓存
里的模块的呢?它是根据你 当前请求的模块的实际路径 与缓存
里存储的模块所对应的路径 是否一致(字母大小写也必须完全匹配) 来判断缓存
中是否有此模块,有,则从缓存
中加载,无,则从node_modules
加载
③ 那怎么从node_modules
加载呢?node
会以当前文件所在目录一层层的往上寻找node_modules
,直到找到node_modules
④ 接下来,一般从node_modules
中会寻找your-module.js 或 your-module.node 或 your-module/index.js 或 your-module/package.json
这几个文件(注意:这个your-module
有可能是@angular/core
这种形式,也有可能是ionic-angular
这种形式)
⑤ 查找到your-module/package.json
这个文件后是怎么实现模块加载的呢?这个package.json
文件里会配置main
属性,对应一个js
文件,比如main: 'lib/ajv.js'
,那么node
加载的就是your-module/lib/ajv.js
这个文件了(注意:可能是${node_modules}/@angular/core/lib/ajv.js
这种形式,也有可能是${node_modules}/ionic-angular/lib/ajv.js
这种形式,${node_modules}
代表③中找到的node_modules
的实际路径)
题外话:
require.resolve(request[, options])
这个方法会解析你请求的模块,返回给你这个模块文件的实际路径