这张图哪里截截图打印出来不清晰的?

浅探node app.js的底层源码加载流程(纯js部分的源码) - CNode技术社区
积分: 1800
思考人生规划
执行了node app.js时发生了什么
从入门开始,我们就写了大量的node app.js,但是不知道大家思考过,当我们执行
node app.js
时,Node这个runtime是如何解释执行我们在app.js中的代码呢?
其实对于Node来说,加载app.js是通过加载源码目录src/node.js的方式,做了真正调用V8引擎解释执行app.js的内容之前的一些预处理。本文的主要目的,是单纯站在js的角度,对涉及到的一些使用js编写的内建模块的源码进行解析,使大家能对Node中的一些行为理解的更加透彻。所以实际上,我们编写的app.js是由src/node.js包裹后被执行的,所以下面首先来分析下,node.js文件究竟做了些什么 。
src/node.js分析
我们首先对src/node.js的代码进行简化剥离,去除一些无关的if选项以及全局的global对象字面量中的变量创建,那么可以得到大致如下的代码,可以看到,其实就是一个匿名函数,实际上,Node源码中的c++部分,会解析这个node.js文件,并且将解析得到的如下匿名函数,传入构造好的process参数,并且执行这个匿名函数,下面对这个匿名函数进行按行的注释分析,来探究此函数如何加载了我们编写的app.js:
(function start(process) {
//创建global对象字面量
this.global =
//核心函数startup
function startup() {
//此处由于无法直接使用require,所以使用在node.js中编写的NativeModule提供的require方法,加载内建模块 events
var EventEmitter = NativeModule.require('events');
process._eventsCount = 0;
//使用setPrototypeOf方法,使得 process对象继承 events类,同时将process对象的构造器依旧指向原本的process.constructor
//这种继承方式,和内建模块中的util.inherits方式是一样的
Object.setPrototypeOf(process, Object.create(EventEmitter.prototype, {
constructor: {
value: process.constructor
//依旧是继js继承的一部分,作用是让process对象,继承 events类的所有非原型链属性
EventEmitter.call(process);
//将完整的events类,赋值给process的EventEmitter属性上备用
process.EventEmitter = EventE
//process.argv[0],原始存储的是环境变量中的node路径,此处将实际的node可执行文文件路径,替换环境变量中的node路径
process.argv[0] = process.execP
//process.argv[1]中存储的是node app.js里的app.js
if (process.argv[1]) {
//调用内建模块的path方法,使用NativeModule.require而不是直接require的原因和上述的events一致
var path = NativeModule.require('path');
//使用path.resolve方法,补全app.js的全路径,并且替换到process.argv[1]中的参数
process.argv[1] = path.resolve(process.argv[1]);
//调用内建模块的module方法
var Module = NativeModule.require('module');
//如果process._preload_modules有内容,则调用module._preloadModules方法,载入预加载的各个模块
startup.preloadModules();
//调用内建模块的module提供的runMain方法(是一个静态方法),启动主函数,app.js,正是在runMain中被包裹加载
Module.runMain();
//主函数入口
startup();
通过上面的注释,可以看到,实际上我们执行
node app.js
时,实际上最终执行了Node中的内建模块中提供的module模块中的runMain方法。下面我们就来分析下runMain方法的逻辑。
lib/module.js中的runMain方法
Module.runMain = function () {
Module._load(process.argv[1], null, true);
process._tickCallback();
以上是runMain方法的源码,可以发现,process.argv[1]存储的是app.js在当前服务器上的全路径,所以真正执行包裹我们编写的app.js的应该是Module.load方法。
顺带提一下,第二行的process.tickCallback()函数,其实就是我们使用process.nextTick的主回调函数,这个函数会把我们往nextTickQueue数组中push进去的回调函数,全部拿出来按照顺序进行处理。这里调用是为了防止在执行runMain之前nextTickQueue数组中已经注册了等待nextTick处理的回调函数。
lib/module.js中的_load方法
Module._load = function (request, parent, isMain) {
//使用Module._resolveFilename方法,获取request(这里就是app.js的全路径)参数的路径
//其中如果request被内建模块命中,则直接返回request,如果不是,则返回该模块的全路径,如果未找到,则throw &Cannot find module request&的err
var filename = Module._resolveFilename(request, parent);
//Module._cache中保存的是所有已加载的模块的缓存,并且以 key(全模块路径)——value(模块内容)的方式进行缓存
var cachedModule = Module._cache[filename];
//如果当前模块全路径,在_cache中查询到对应的缓存,则直接返回该缓存的exports属性对应的值
//这里其实就是module.exports的内容
if (cachedModule) {
return cachedModule.
//如果该模块匹配命中内建模块,则调用NativeModule.require方法加载该内建模块
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
//生成Module的一个实例
var module = new Module(filename, parent);
//isMain表示是否为main函数,这里的app.js显然是main函数,所以此处为true
if (isMain) {
//给process全局变量增加mainModule属性
process.mainModule =
module.id = '.';
//将当前的模块以 key(全模块路径)——value(模块内容)的形式添加入_cache缓存
Module._cache[filename] =
//标记位,用来表示模块加载出错时,删除对应的缓存
var hadException =
//核心方法,真正加载filename这个全路径对应的js文件,这里就是app.js的全路径
module.load(filename);
hadException =
} finally {
if (hadException) {
delete Module._cache[filename];
//这里可以看到,require方法,最终返回的就是module实例的exports属性对应的值
return module.
以上是对module._load方法的注释解析,在这里,我们其实可以明白如下的几个原理:
1.node app.js最后就和普通的require模块一样,最后执行了相当于执行了require(‘app.js全路径’),只是此处的require是由runMain方法发起的;
2.node中执行require一个模块,匹配的顺序始终是最优先匹配内建模块(http, https, path等嵌入Node本身的api库),再匹配项目中的模块
3.每一个require的动作,其实都生成了Module的一个实例,并且,require得到的是Module实例的exports属性(this.exports)的值,所以对应的,我们想到在一个js文件中导出方法给外部require,要么就是exports = func,要么就是module.exports = func
以上几个原理,能帮助我们更加深刻的理解node加载模块时的一些细节,我们接下来分析,可以看到,核心方法module.load(filename),应该是下一个需要分析的核心流程,而且从名字上也能看出来,_load是一个内部方法,load是其本体~
lib/module.js中的load方法
Module.prototype.load = function (filename) {
//获取需要加载的文件的扩展名,默认为.js,这里的app.js的全路径扩展名,则必然为.js
var extension = path.extname(filename) || '.js';
//判断上一步获取到的扩展名是否符合预设,只支持 '.js', '.json'和'.node'三种,如果不符合,重置为默认的.js
if (!Module._extensions[extension]) extension = '.js';
//调用Module的_extensions['.js']对应的方法,传入this和文件全路径,进行最终的加载
Module._extensions[extension](this, filename);
//将module.loaded重置为true,表示加载完毕
this.loaded =
依旧是按行的注释,此函数逻辑比较简单,那么此处的app.js加载,最终会调用
Module.extensions[‘.js’]的方法进行加载,注意的此处的this指的是上述load函数中的module(module = new Module(filename, parent)),filename依旧是app.js的全路径。下面继续分析Module._extensions[‘.js’]方法的内容。
lib/module.js中的_extensions加载js文件方法
Module._extensions['.js'] = function (module, filename) {
//调用内建模块中fs中的readFileSync方法,以'utf8'的形式读取文件内容,此处其实就是根据app.js的全路径
var content = fs.readFileSync(filename, 'utf8');
//首先使用stripBOM方法,将读取出的内容中的utf-8编码特殊的头部信息剥离掉,然后调用_compile方法进行编译
module._compile(internalModule.stripBOM(content), filename);
这个方法注释解析如上,也是比较简单的,值得注意的是,我们编写的js文件,被Node加载时,是以utf-8的编码进行读取的,读取完成后,首先剥离掉utf-8编码的特殊头部信息,再进行编译的。下面来看下compile方法的实现,此处传入的参数是剥离掉utf-8信息之后的app.js的内容,以及app.js的全路径。在分析compile之前,我们需要先看两个函数,有助于接下来最终的_compile方法的理解。
lib/module.js中的wrap方法
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
NativeModule.wrap = function (script) {
//wrap方法在script的外部,包裹了成形如:
/*(function (exports, require, module, __filename, __dirname) {
//传入的script
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
这里比较简单,大家看下就可以了。
lib/module.js中的_compile方法
Module.prototype._compile = function (content, filename) {
//如果js文件的内容里面存在释伴,则将释伴内容替换为空字符串,释伴是*inx系统中的指定执行变量的一个方式
//比如:#!/usr/bin/env node,那该js文件可以无需添加node而自动执行
content = content.replace(shebangRe, '');
//wrap方法,会在content的外部,包一层函数,具体见Module.wrap方法
var wrapper = Module.wrap(content);
//通过调用内建模块vm的runInThisContext方法,编译该经过包裹后的js代码,返回的得到的是一个函数
//形如:(function (exports, require, module, __filename, __dirname) { app.js的代码内容 });
var compiledWrapper = runInThisContext(wrapper, {filename: filename, lineOffset: 0});
//获取dirname信息
const dirname = path.dirname(filename);
//这里是得到require方法,具体可见makeRequireFunction函数的解析,此处传入的this就是Module的实例
const require = internalModule.makeRequireFunction.call(this);
//这里是一个老API接口调用的异常抛出,那么从Node v4.x开始,不再支持require.paths的方法传入匹配path,修改为
//process.env.NODE_PATH的赋值,如果继续调用require.paths,那么就会抛出这个异常
Object.defineProperty(require, 'paths', {
get: function () {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
//同样的老API接口调用的异常抛出,require.registerExtension已经被require.extensions方法替代
require.registerExtension = function () {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
//定义参数
const args = [this.exports, require, this, filename, dirname];
//绑定上述的compiledWrapper得到的匿名函数,并且绑定this到module.exports,同时传入
//module.exports, require, module, filename, dirname这几个参数
//至此,我们的app.js最终被执行,执行的方式形如:
/*let fn = (function(exports, require, module, __filename, __dirname){
//我们编写的app.js代码
fn.apply(module.exports, [module.exports, require, module, filename, dirname]);
//所以,这里可以明白,为什么在app.js里面,我们可以直接调用require, module.exports, exports等方法~
return compiledWrapper.apply(this.exports, args);
这个函数其实就是最终编译执行app.js的地方,通过源码的注释分析,我们可以发现,最终的app.js,其实被以形如:
let fn = (function(exports, require, module, __filename, __dirname){
//我们编写的app.js代码
fn.apply(module.exports, [module.exports, require, module, filename, dirname]);
执行了,我们的app.js在外部被包裹了一层函数,这样防止了变量的全局污染,也是为什么我们在node的空白js文件中直接执行console.log(this),会得到一个空对象,而不是像浏览器中的那样指向global,其实代码里解释的很清楚,因为我们编写的js文件一定会被wrap方法在外部包裹一个函数,并且执行的时候this指针被绑定到module.exports上去,所以会出现这样的现象。
本文算是打算开始研究学习Node c++部分源代码的热身吧,因为对js比较熟悉,所以先开始看lib目录下的一些内建模块的js实现和最初被加载的位于src目录下的node.js文件的实现方式,收获也是蛮大的。
比如在为什么在很多npm源码库里面,我们会看到exports = module.exports = {}的写法,比如为什么module.exports = {}这样赋值后,exports.A = funcA就无效了。
赞!在朴大的《深入浅出NodeJS》中提到过这个把js文件包裹起来的内容,这下楼主给出了比较详细的解析!
看了朴灵大神的书,很想能深入研究学习下,哈哈
CNode 社区为国内最专业的 Node.js 开源技术社区,致力于 Node.js 的技术研究。
服务器赞助商为
,存储赞助商为
,由提供应用性能服务。
新手搭建 Node.js 服务器,推荐使用无需备案的Nodejs源码解析之module
Nodejs源码解析之module
编辑:www.fx114.net
本篇文章主要介绍了"Nodejs源码解析之module",主要涉及到Nodejs源码解析之module方面的内容,对于Nodejs源码解析之module感兴趣的同学可以参考一下。
http://blog.csdn.net/leoleocs/article/details/#
module管理是Nodejs中比较有特色的部分,官方有详细的文档&哪里介绍了一些基本的使用规则,主要的内容如下,
文件和模块之间是一一对应关系:使用方法就是require,后续源码解析中会详细介绍什么是require,以及如何实现的。 文件的名字就是一个id,也就是标志符。如何访问和被确认为主模块: 简单的说就是被nodejs启动的模块为主模块,其他都不是,这个可以在源码中说明。模块的导入:作为单个文件,目录,以及node_module如何导入。这里在源码中也有体现缓存: 被require导入的模块只导入一次,不会有多次,这个是靠cache来实现的,查找的关键字是文件(模块)的文件名(含全路径)。 同时需要主要循环依赖问题module 对象的具体的含义与使用,这个也在源码中有实现
这里主要解析module.js 的全部源码和部分的代码,以说明上述的问题,在review module.js 的源码前,推荐下面两遍博文:&
module.js的导入
module.js是由node.js在Nodejs程序启动的时候导入的。module.js中使用的require函数是在node.js文件中定义的。 具体的代码在node.js中,代码分为两段:
// 程序启动时候,会利用NativeModule去require我们的module.js函数
& & & // 下面会详细介绍NativeModule
& & & var Module = NativeModule.require('module');
& & & .............(省去)
& & & } else {
& & & & // Main entry point into most programs:
& & & & // 这个就是文档中所说的主模块问题,也就是利用node app.js启动程序时候,
& & & & // app.js就是主模块,也就是module.js里面定义的runMain函数。
& & & & Module.runMain();
下面详细说明NativeModule的使用
// 对象构造函数
& & function NativeModule(id) {
& & // 文件名,自动加上了js后缀,说明其仅仅解析js的module文件
& & this.filename = id + '.js';
& & // 用于记录本模块的标志符
& & this.id =
& & // 模块的导出对象
& & this.exports = {};
& & // 是否导出标志符
& & this.loaded =
& // 内部的模块, 具体可以查看 process.binding的用法
& NativeModule._source = process.binding('natives');
& // 用于做NativeModule的内部缓存
& NativeModule._cache = {};
& // 这个是在module.js中使用的require函数
& NativeModule.require = function(id) {
& & // 特殊处理,如果是native_module,直接返回
& & if (id == 'native_module') {
& & & return NativeM
& & &// 查看是否有缓存,
& & var cached = NativeModule.getCached(id);
& & if (cached) {
& & &// 如果有,直接返回导出的对象
& & & return cached.
& & // 是否在NativeModule中存在
& & if (!NativeModule.exists(id)) {
& & & throw new Error('No such native module ' + id);
& & // 这个应该是和C/C++的系统模块交互
& & process.moduleLoadList.push('NativeModule ' + id);
& & // 生成一个新的NativeModule对象
& & var nativeModule = new NativeModule(id);
& & &// 做缓存
& & nativeModule.cache();
& & // 编译模块
& & pile();
& & // 导出模块的对象
& & return nativeModule.
& // 查找是否有缓存
& NativeModule.getCached = function(id) {
& & return NativeModule._cache[id];
& // 查找id是否存在,从代码上可以看出NativeModule要求在c/c++代码中有体现
& NativeModule.exists = function(id) {
& & return NativeModule._source.hasOwnProperty(id);
& // 获取source
& NativeModule.getSource = function(id) {
& & return NativeModule._source[id];
& // 对于script的封装,这个是后续理解module,exports等的关键。任何的require
& //一个文件或者模块其实就是将文件里面的内容,封装成一个函数
& NativeModule.wrap = function(script) {
& & return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
& // 具体的函数头和尾
& NativeModule.wrapper = [
& & '(function (exports, require, module, __filename, __dirname) { ',
& & '\n});'
& pile = function() {
& & var source = NativeModule.getSource(this.id);
& & source = NativeModule.wrap(source);
& & // 这个是javascript虚拟机的功能 后续会有详细介绍
& & var fn = runInThisContext(source, { filename: this.filename });
& & // 查看上述的wrap 函数定义,也就是说在module.js中使用的module
& & // 其实是NativeModule对象
& & // 使用的require函数,其实是NativeModule.require
& & fn(this.exports, NativeModule.require, this, this.filename);
& & &// 模块已经导入
& & this.loaded =
& // 增加cache
& NativeModule.prototype.cache = function() {
& & NativeModule._cache[this.id] =
由于module模块的内容比较多,分如下重点函数进行源码分析,然后再分析帮助函数
Require函数详解
下面是require函数的源码,也就是我们通常用require(./test)时候,这里需要强调的是,require不是关键字,而是一个函数
// Loads a module at the given file path. Returns that module's
// `exports` property.
// 导入模块,并且返回模块的exports属性。
Module.prototype.require = function(path) {
& // 参数检查,确保path不为空
& assert(path, 'missing path');
& // 参数检查,确保path为string类型
& assert(util.isString(path), 'path must be a string');
& // 直接调用_load函数返回,注意参数多了一个this,也就是本module的对象
& return Module._load(path, this);
// Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call `NativeModule.require()` with the
// & &filename and return the result.
// 3. Otherwise, create a new module for the file and save it to the cache.
// & &Then have it load &the file contents before returning its exports
// & &object.
// 上述的介绍翻译。
// 对要求的文件检查缓存,
// @request就是对应的要导入的file或者目录,
// @parent其实是谁导入了该模块,从上述的源码可以看出,其实是this。
// @isMain 标志是否为主文件,这里只有从node.js 调用的才是,其他都不是。
// 对于缓存,会做下面的事情:
// 1. 如果模块以及存在在缓存中,直接返回。
// 2. 如果模块是native的,直接调用NativeModule.require()并且返回。
// 3. 否则,创建一个新的module对象,保存在缓存中,并且导入文件内容,然后返回exports对象。
Module._load = function(request, parent, isMain) {
& // 添加log,看谁导入了当前的模块
& if (parent) {
& & debug('Module._load REQUEST &' + (request) + ' parent: ' + parent.id);
& //找到当前的需要解析的文件名,以后会详解_resolveFilename
& var filename = Module._resolveFilename(request, parent);
& &// 步骤1:如果已经有的缓存,直接返回缓存的exports
& var cachedModule = Module._cache[filename];
& if (cachedModule) {
& & return cachedModule.
& // 步骤2:如果在自然模块中存在,直接使用自然模块进行解析
& if (NativeModule.exists(filename)) {
& & // REPL is a special case, because it needs the real require.
& & if (filename == 'repl') {
& & & var replModule = new Module('repl');
& & & replModule._compile(NativeModule.getSource('repl'), 'repl.js');
& & & NativeModule._cache.repl = replM
& & & return replModule.
& & debug('load native module ' + request);
& & return NativeModule.require(filename);
& // 创建Module对象。
& var module = new Module(filename, parent);
& // 是否为主模块,
& if (isMain) {
& & &// 主模块的话,需要将当前的module赋值给process.mainModule
& & process.mainModule =
& & // 主模块的id特殊的赋值为&.&
& & module.id = '.';
& // 将创建的模块cache起来
& Module._cache[filename] =
& // 确保是否有异常
& var hadException =
& & // 做真正的导入模块的操作,下面会详解该函数
& & module.load(filename);
& & hadException =
& } finally {
& & &// 如果有异常,直接删除上述的缓存
& & if (hadException) {
& & & delete Module._cache[filename];
& // 返回新创建模块的exports
& return module.
// Given a file name, pass it to the proper extension handler.
// 指定一个文件名,导入模块,调用适当扩展处理函数,当前主要是js,json,和node
Module.prototype.load = function(filename) {
& //增加log,当前导入什么文件,id是什么&
& debug('load ' + JSON.stringify(filename) +
& & & & ' for module ' + JSON.stringify(this.id));
& // 确保当前模块没有被载入
& assert(!this.loaded);
& // 赋值当前模块的文件名
& this.filename =
& // 当前的path
& this.paths = Module._nodeModulePaths(path.dirname(filename));
& // 当前文件的后缀
& var extension = path.extname(filename) || '.js';
& // 确认默认的后缀都*.js
& if (!Module._extensions[extension]) extension = '.js';
& // 根据后缀的解析函数来做解析
& Module._extensions[extension](this, filename);
& this.loaded =
下面是nodejs支持的三种后缀:&
// Native extension for .js
// js后缀的处理
Module._extensions['.js'] = function(module, filename) {
& //直接同步的读入文件的内容。&
& var content = fs.readFileSync(filename, 'utf8');
& // 然后调用_compile进行编译。下面会分析该函数
& module._compile(stripBOM(content), filename);
// Native extension for .json
// 对于json文件的处理
Module._extensions['.json'] = function(module, filename) {
& //直接同步的读入文件的内容。&
& var content = fs.readFileSync(filename, 'utf8');
& &// 直接将模块的exports赋值为json文件的内容
& & module.exports = JSON.parse(stripBOM(content));
& } catch (err) {
& & // 异常处理
& & err.message = filename + ': ' + err.
//node文件的打开处理,通常为C/C++文件。
Module._extensions['.node'] = process.
下面就分析最后的一个函数_compile:
// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
// 这个函数会给出require, module, exports等帮助变量给文件
// @content 主要是js文件的主要内容
// @filename 是js文件的文件名
Module.prototype._compile = function(content, filename) {
& // self就是一个帮助变量,代表的this
& var self =
& // remove shebang
& // 去掉一些注释(shebang)
& content = content.replace(/^\#\!.*/, '');
& &// 其实模块中的require就是这个函数
& &// 其仅仅是一个对module中的require函数的封装。
& function require(path) {
& & return self.require(path);
& //resolve 函数,这个会解释文档中的module导入的路径问题,是单个文件,目录还是模块&
& require.resolve = function(request) {
& & return Module._resolveFilename(request, self);
& // 禁止使用require中的paths路径
& Object.defineProperty(require, 'paths', { get: function() {
& & throw new Error('require.paths is removed. Use ' +
& & & & & & & & & & 'node_modules folders, or the NODE_PATH ' +
& & & & & & & & & & 'environment variable instead.');
& //注意require.main就是主模块&
& require.main = process.mainM
& // Enable support to add extra extension types
& // 将Module._extensions赋值给require
& require.extensions = Module._
& require.registerExtension = function() {
& & throw new Error('require.registerExtension() removed. Use ' +
& & & & & & & & & & 'require.extensions instead.');
& //将缓存也赋值给require
& // require一会是函数,一会又像是对象,其实都是对象:)
& require.cache = Module._
& // 获取当前的文件的路径
& var dirname = path.dirname(filename);
& // 当NODE_MODULE_CONTEXTS为1的时候才可以调用,也就是说,所有的模块都在一个环境中,无需
& // 模块来,这个好像通常情况下不会被执行。
& // Module._contextLoad = (+process.env['NODE_MODULE_CONTEXTS'] & 0);
& if (Module._contextLoad) {
& & if (self.id !== '.') {
& & & debug('load submodule');
& & & // not root module
& & & var sandbox = {};
& & & for (var k in global) {
& & & & sandbox[k] = global[k];
& & & sandbox.require =
& & & sandbox.exports = self.
& & & sandbox.__filename =
& & & sandbox.__dirname =
& & & sandbox.module =
& & & sandbox.global =
& & & sandbox.root =
& & & return runInNewContext(content, sandbox, { filename: filename });
& & debug('load root module');
& & // root module
& & global.require =
& & global.exports = self.
& & global.__filename =
& & global.__dirname =
& & global.module =
& & return runInThisContext(content, { filename: filename });
& // create wrapper function
& // 这里的wrap函数就是node.js中的函数,会将文件中的内容封装成一个函数。
& var wrapper = Module.wrap(content);
& // 编译内容,返回函数
& var compiledWrapper = runInThisContext(wrapper, { filename: filename });
& // 处理debug模式,
& if (global.v8debug) {
& & if (!resolvedArgv) {
& & & // we enter the repl if we're not given a filename argument.
& & & if (process.argv[1]) {
& & & & resolvedArgv = Module._resolveFilename(process.argv[1], null);
& & & } else {
& & & & resolvedArgv = 'repl';
& & // Set breakpoint on module start
& & if (filename === resolvedArgv) {
& & & global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
& // 直接调用wrapper函数,将module模块中的exports,本函数中的require,&
& //self也就是新创建的module作为参数传递给模块,进行执行。
& // filename, dirname作为参数传递过去
& // 这就是为什么我们可以直接在module文件中,直接访问exports, module, require函数的原因
& var args = [self.exports, require, self, filename, dirname];
& return compiledWrapper.apply(self.exports, args);
所以,从上面的源码分析中,我们知道了缓存是如何实现的, module中的变量如exports,id,filename是如何能得到访问的。
module路径解析
这里先可以看一下官方文章中的内容:
require(X) from module at path Y
1. If X is a core module, // 如果是核心模块,
& &a. return the core module // 直接返回核心模块,这里的核心模块是指nodejs下lib的内容
& &b. STOP 返回
2. If X begins with './' or '/' or '../' // 如果文件以 &./&, &/&,或者 &../&形式,
& &a. LOAD_AS_FILE(Y + X) // 导入一个文件 返回,如何处理导入文件
& &b. LOAD_AS_DIRECTORY(Y + X) // 导入一个目录,返回 如何导入目录,看后面
3. LOAD_NODE_MODULES(X, dirname(Y)) // 导入一个NODE_MODULE,返回。
4. THROW &not found& // 上述都没找到,直接排出没找到的异常。
LOAD_AS_FILE(X) // 导入一个文件按
1. If X is a file, load X as JavaScript text. &STOP &// 如果X是一个文件,作为js文件导入,直接返回。直接停止
2. If X.js is a file, load X.js as JavaScript text. &STOP //加上js后缀为一个文件,直接作为js文件导入 直接停止
3. If X.json is a file, parse X.json to a JavaScript Object. &STOP//加上json后缀为一个文件,直接作为json文件导入 直接停止
4. If X.node is a file, load X.node as binary addon. &STOP//加上node后缀为一个文件,直接作为c/c++ addon文件导入 直接停止
LOAD_AS_DIRECTORY(X) 如何导入一个目录的处理方式
1. If X/package.json is a file, // 查找X/package.json是否存在
& &a. Parse X/package.json, and look for &main& field. //查找json file中是否有main
& &b. let M = X + (json main field) // 生成新的文件
& &c. LOAD_AS_FILE(M) // 作为文件导入。
2. If X/index.js is a file, load X/index.js as JavaScript text. &STOP // 查看是否存在 X/index.js,如果存在,作为js文件导入
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP // 查看是否存在 X/index.json,如果存在,导入json文件作为js对象
4. If X/index.node is a file, load X/index.node as binary addon. &STOP// 查看是否存在 X/index.node,如果存在,导入c/c++的 addon导入
LOAD_NODE_MODULES(X, START) // 导入node_module的步骤
1. let DIRS=NODE_MODULES_PATHS(START) // 返回一个文件目录内容
2. for each DIR in DIRS: & & & // 对于目录里的内容
& &a. LOAD_AS_FILE(DIR/X) & & &// 作为文件导入。 作为文件导入,查看LOAD_AS_FILE
& &b. LOAD_AS_DIRECTORY(DIR/X) // 或者作为一个目录导入, 查看LOAD_AS_DIRECTORY
// 请注意,这里仅仅是说还是作为一个具体的文件或者目录,如果有一个找到了,就需要停止,而不是真的要导入文件文件里面的所有内容
NODE_MODULES_PATHS(START) // 具体NODE_MODULES文件目录算法
1. let PARTS = path split(START)&
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I &= 0,
& &a. if PARTS[I] = &node_modules& CONTINUE
& &c. DIR = path join(PARTS[0 .. I] + &node_modules&)
& &b. DIRS = DIRS + DIR
& &c. let I = I - 1
5. return DIRS
基本的思想都在文档中做了详细的说明, 那么下面分析源代码是如何实现的。废话少说,直接源代码分析:
// 这个函数就是在load的时候调用的,也就是说其负责具体filename的文件查找。
// 所以,从这里可以看出,无论怎么处理,require仅仅能导入的是一个文件模块,不能是一个目录,
// 有目录的说法,仅仅是因为可以在目录上查找具体的文件。这也就是文档中所说的文件和目录的一一对应关系
Module._resolveFilename = function(request, parent) {
& //如果是NativeModule中已经存在,直接用,这个其实就是core 模块,文档中的第一种情况
& if (NativeModule.exists(request)) {
& // 根据文件名称,调用_resolveLookupPaths来查找具体的文件模块
& var resolvedModule = Module._resolveLookupPaths(request, parent);
& // 上述返回的id
& var id = resolvedModule[0];
& // 上述返回的具体的目录文件夹
& var paths = resolvedModule[1];
& // look up the filename first, since that's the cache key.
& // 输出具体的id和paths
& debug('looking for ' + JSON.stringify(id) +
& & & & ' in ' + JSON.stringify(paths));
& // 找到具体的文件名称,以返回。
& var filename = Module._findPath(request, paths);
& // 处理文件找不到的情况
& if (!filename) {
& & var err = new Error(&Cannot find module '& + request + &'&);
& & err.code = 'MODULE_NOT_FOUND';
& // 返回具体的文件
// 从上面的文档可以看出,在处理核心模块后,其是依靠_resolveLookupPaths和_findPath这两个
// 帮助函数来查找具体的文件模块。&
// _resolveLookupPaths: 该函数用于查找当前文件“可能的路径”
// _findPath: 根据上述的路径,根据优先级选择一个具体的文件,如官方文档中的文件
Module._resolveLookupPaths = function(request, parent) {
& //依然先检查核心模块&
& if (NativeModule.exists(request)) {
& & return [request, []];
& // 查找request的标志符,如果不以&./& 或者'..'开头
& var start = request.substring(0, 2);
& if (start !== './' && start !== '..') {
& & // 这种情况直接返回nodejs系统路径modulePaths,这个是在Module._initPaths 函数中设置的,
& & // 有兴趣可以自己分析一下,很简单的函数
& & var paths = moduleP
& & if (parent) {
& & & &// 设置一下父亲的路径,其实就是谁导入了当前模块
& & & if (!parent.paths) parent.paths = [];
& & & paths = parent.paths.concat(paths);
& & //直接返回
& & return [request, paths];
& // with --eval, parent.id is not set and parent.filename is null
& // 处理父亲模块为空的情况,这种情况我认为一般就是主模块
& if (!parent || !parent.id || !parent.filename) {
& & // make require('./path/to/foo') work - normally the path is taken
& & // from realpath(__filename) but with eval there is no filename
& & // 生成新的目录, 在系统目录modulePaths,当前目录和&node_modules&作为候选的路径
& & // 对于node_modules,可以参考_nodeModulePaths函数。
& & var mainPaths = ['.'].concat(modulePaths);
& & mainPaths = Module._nodeModulePaths('.').concat(mainPaths);
& & return [request, mainPaths];
& // Is the parent an index module?
& // We can assume the parent has a valid extension,
& // as it already has been accepted as a module.
& // 处理父亲模块是否为index模块,
& var isIndex = /^index\.\w+?$/.test(path.basename(parent.filename));
& var parentIdPath = isIndex ? parent.id : path.dirname(parent.id);
& // 返回request中的具体的id
& var id = path.resolve(parentIdPath, request);
& // make sure require('./path') and require('path') get distinct ids, even
& // when called from the toplevel js file
& // 确保require('./path') and require('path') 的id不一样。应该会做不同的缓存,所以,我们
& if (parentIdPath === '.' && id.indexOf('/') === -1) {
& & id = './' +
& debug('RELATIVE: requested:' + request +
& & & & ' set ID to: ' + id + ' from ' + parent.id);
& //返回id和具体的父亲模块的文件夹
& //注意: 也就是说,当我们以&./& 等方式require是,都是以当前父模块为对象路径的
& return [id, [path.dirname(parent.filename)]];
接下来分析一下:_findPath&
// 从候选路径中选择
Module._findPath = function(request, paths) {
& // 等到具体的文件扩展,现在有js,json 和node
& var exts = Object.keys(Module._extensions);
& //如果以绝对路径开头,直接重置可选路径,所以这个windows下和linux下应该不一样,
& //这点应该是对linux来说的,所以,我们最好不要以/开头导入模块
& if (request.charAt(0) === '/') {
& & paths = [''];
& var trailingSlash = (request.slice(-1) === '/');
& // 这里是做文件扫描的cache,防止重复查找
& var cacheKey = JSON.stringify({request: request, paths: paths});
& if (Module._pathCache[cacheKey]) {
& & // 如果cache已经有了,直接返回
& & return Module._pathCache[cacheKey];
& // For each path
& // 从每个候选路径中查找文件
& for (var i = 0, PL = paths. i & PL; i++) {
& & var basePath = path.resolve(paths[i], request);
& & if (!trailingSlash) {
& & & // try to join the request to the path
& & & // 尝试一下当前文件是否存在
& & & filename = tryFile(basePath);
& & & // 尝试一下当前文件加上后缀文件是否存在
& & & if (!filename && !trailingSlash) {
& & & & // try it with each of the extensions
& & & & filename = tryExtensions(basePath, exts);
& & // 尝试一下当前的package.json文件是否存在
& & if (!filename) {
& & & filename = tryPackage(basePath, exts);
& & &// 尝试一下index文件加上后缀是否存在
& & if (!filename) {
& & & // try it with each of the extensions at &index&
& & & filename = tryExtensions(path.resolve(basePath, 'index'), exts);
& & if (filename) {
& & & // 增加到文件缓存中
& & & Module._pathCache[cacheKey] =
// 所以从这里可以看出,对于具体的文件的优先级:
// 1. 具体文件。
// 2. 加上后缀。
// 3. package.json
// 4 &index加上后缀
// 候选路径以当前文件夹,nodejs系统文件夹和node_module中的文件夹为候选,以上述顺序找到任意一个,
// 就直接返回
还有一些帮助函数,如readPackage, tryPackage,tryFile,_initPaths没有做详细的说明。
上述就是module的全部重要的源码说明。
一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!
二、互相尊重,对自己的言论和行为负责。
本文标题:
本页链接:

我要回帖

更多关于 苹果截图出来全白的 的文章

 

随机推荐