引题

用过node的同学应该都知道require是用来加载模块的,那你是否存在如下的疑问呢?

1. require(path)是如何依据path找到对应module呢?

2. 为何在模块定义中,一定要通过module.exports暴漏出接口?module.exports与require存在什么关系

对上述问题进行概括可以抽象出如下两个问题:

1. module的路径分析
2. 文件加载

切入

   首先来直观地看看require是什么? 

// node环境下执行:
console.log(require.toString) //输入结果为:
'function require(path) {\n return self.require(path);\n }'

  上述代码说明require函数仅仅是module.require的封装,这样就需要查看node中的module源代码了。

加载模块的方式

首先来直观来认识一下node的模块加载方式有哪些方式:

case 1:

// 'path'为node的核心模块
var path = require('path')

case2:

// a.js,路径为: basePath/a.js
var myModule = require('./my-module')
// my-module的路径为basePath/node_modules/myModule.js

case 3:

// a.js, 路径: basePath/a.js
var main = require('./')
// basePath下还包括package.json, index.js

路径解析

在node的官方API中,我们可以找到这段描述:

To get the exact filename that will be loaded when require() is called, use the require.resolve() function.

Putting together all of the above, here is the high-level algorithm in pseudocode of what require.resolve does:

......

试试在node环境下用用require.resolve这个API:

require.resolve('./a.js')
// 这样就得到a.js的绝对路径

为了探索缘由,就从node核心代码中的mdoule.js找答案吧:

require.resolve = function(request) {
  return Module._resolveFilename(request, self);
} Module._resolveFilename = function(request, parent) {
  // 判断是否为node的核心模块
  if (NativeModule.exists(request)) {
  return request;
 }
// 得到查询路径,格式为数组:[id, [paths]]
var resolvedModule = Module._resolveLookupPaths(request, parent); var paths = resolvedModule[1];
  // 根据path、fileName得到绝对路径
  var filename = Module._findPath(request, paths);
return filename;
}

那Module._resolveLookupPaths是如何得到所有查询路径的呢?

  1. 为node的核心模块,stop
  2. 以./或../开头,本地查找, stop
  3. 沿着文件树,得到node_module的所有路径,直到/node_modules,在node_module中查找,stop
  4. path为目录,则检查package.json文件是否存在main属性,否则默认为index.js
  5. 最后返回new Error('Cannot find module"' + request + '"');

模块加载

先看require的源代码:

// 我们经常使用的require函数
function require(path) {
return self.require(path);
}
// 调用_load函数,加载所需的模块
Module.prototype.require = function(path) {
  return Module._load(path, this);
}

这样模块函数的调用连接到了Module._load函数:

Module.cache = {};
Module._load = function() {
  // 检测模块是否已经加载过
  var cachedModule = Module._cache[filename];
if (cachedModule) {
  return cachedModule.exports;
}
// 模块还未加载,则为模块创建module实例
var module = new Module(filename, parent);
  // 新创建的实例存储于cache中
 Module._cache[filename] = module;
// 开始获取模块的内容
module.load(filename);
// 对外提供接口
return module.exports;
}

  接下来问题的关键就变成了module.load,该方法用于获取module的内容,然后进行解析:

Module.prototype.load = function(filename) {
  // 解析出文件的后缀, 存在['.js', '.json', 'node']三种后缀
  var extension = path.extname(filename) || '.js';
  // 根据后缀,获取相关的模块
  Module._extensions[extension](this, filename);
}

  node会匹配按照.js、.json、.node三种格式进行模块匹配,根据文件类型的不同采取不同的加载策略,但是以实际开发中以加载.js最多,该种策略最后需要调用Module.prototype._compile进行编译处理:

Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
}; Module.prototype._compile = function(content, filename) {
  //将内容放入到(function() { content }),形成闭包,创建私有作用域
  var wrapper = Module.wrap(content);
  // bind新的执行上下文
  var compiledWrapper = runInThisContext(wrapper, { filename: filename });
  // 向外暴漏接口:module.exports, require, module,__filename, __dirname,
  var args = [self.exports, require, self, filename, dirname];
  return compiledWrapper.apply(self.exports, args);
}

  这样,我们就可以在require来获取相应地module。

结论
      node现在这么火,各种优势铺天盖地涌来,会让刚刚入行的人觉得深不可测,因而往往会让人望而却步。但是只要我们敢于突破第一步,深入下来仔细分析,就会发现其实没有那么晦涩难懂,踏出第一步真的很关键!

参考资料

http://thenodeway.io/posts/get-fancy/how-require-actually-works/

https://github.com/joyent/node/blob/master/lib/module.js

http://nodejs.org/api/modules.html

https://github.com/substack/node-resolve

你知道require是什么吗?的更多相关文章

  1. WCF : 修复 Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service 问题

    摘要 : 最近遇到了一个奇怪的 WCF 安全配置问题, WCF Service 上面配置了Windows Authentication. IIS上也启用了 Windows Authentication ...

  2. webpack解惑:require的五种用法

    我之前在 <前端搭环境之从入门到放弃>这篇文章中吐槽过,webpack中可以写commonjs格式的require同步语法,可以写AMD格式的require回调语法,还有一个require ...

  3. express全局安装后无法通过require使用

    今天入门了一下express,首先安装依赖. npm install express -g; npm install body-parser -g; npm install cookie-parser ...

  4. require() 源码解读

    2009年,Node.js 项目诞生,所有模块一律为 CommonJS 格式. 时至今日,Node.js 的模块仓库 npmjs.com ,已经存放了15万个模块,其中绝大部分都是 CommonJS ...

  5. 项目开发(Require + E.js)

    最近在做的几个项目,分别用了不同的框架跟方式,有个H5的项目,用了vue框架, 这个项目我还没有正式加入进去, 等手头的这个项目完成就可以去搞vue了, 现在手头的这个项目是一个招聘的项目, 用到了N ...

  6. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

  7. Torch Problems: require some packages doesn't work

    I've recently got a problem. require 'cutorch' doesn't work. But it was ok yesterday, although I hav ...

  8. javascript模块化编程(三):require.js用法

    本文来自阮一峰 这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库require.js. 一.为什么要用require ...

  9. PHP中include()与require()的区别说明

    require 的使用方法如 require("MyRequireFile.php"); .这个函数通常放在 PHP 程序的最前面,PHP 程序在执行前,就会先读入 require ...

  10. CCLuaLoadChunksFromZIP加载后的require路径问题

    对于require来说,在LUA中的机制就是搜索path路径了.但对于CCLuaLoadChunksFromZIP加载的LUA文件来说,require的路径又是怎么样的呢? 我在服务器上有一个 oox ...

随机推荐

  1. Linux第八次学习笔记

    系统级I/O 输入/输出(I/O)是在主存和外部设备之间拷贝数据的过程. 输入操作是从I/O设备拷贝数据到主存. I/O→主存 输出操作是从主存拷贝数据到I/O设备. 主存→I/O Unix I/O ...

  2. RF源码阅读(碎片纪录)-Python积木之contextlib

    参考页面: http://docs.python.org/2/library/contextlib.html contextlib是为了配合with语句来使用的.使用起来更加简洁.本来想写一下,这位同 ...

  3. win8安装mean.io详解

    最近,老大说要安装mean.io,然后……我的win8华丽丽的就上战场了……这期间真是安装得要生要死……最终也终于“不辱使命”成功安装上了…… 废话不多说,进入正题啦 前提当然是电脑有node.环境… ...

  4. Android空闲教室查询-资料

    这是去年某课程的一个称作“研究型学习”的东西的报告的展示PPT,有点失败的是这个APP的名字起得不太好……PPT上的功能都实现了,其他功能都没有.一年了,程序都忘差不多了,也暂时没有时间分享.就先把P ...

  5. 技术分析:Femtocell家庭基站通信截获、伪造任意短信

    阿里移动安全团队与中国泰尔实验室无线技术部的通信专家们一起,联合对国内运营商某型Femtocell基站进行了安全分析,发现多枚重大漏洞,可导致用户的短信.通话.数据流量被窃听.恶意攻击者可以在免费申领 ...

  6. JavaScript并非“按值传递”

    置顶文章:<纯CSS打造银色MacBook Air(完整版)> 上一篇:<拥Bootstrap入怀--模态框(modal)篇> 作者主页:myvin 博主QQ:85139910 ...

  7. JSTL(JSP Standard Tag Library)读书笔记

    分类                                       Preifx                                          范例 核心标签库--- ...

  8. 自己编写redis客户端[deerlet-redis-client],分享与招募。

    引言 最近工作上有需要使用redis,于是便心血来潮打算自己写一个Java客户端.经过两天的努力,目前该客户端已经基本成型.不过可惜的是,由于redis的命令众多,因此LZ还需要慢慢扩展它去支持更多的 ...

  9. Git.Framework 框架随手记--ORM编辑删除

    前面一篇文章<Git.Framework 框架随手记--ORM新增操作>主要讲解了如何使用Git.Framework往数据库中添加数据.其操作过程相对简单,本章主要记录如何编辑数据和修改数 ...

  10. C# 无法识别的转义序列

    解决这个问题头两种方法:1.加个"\"进行转义:2.在前面加个@ 示例:我要进入D盘下video文件夹中的ysxs文件夹,写法分别为: D:\\video\\ysxs @" ...