- modulejs的导入
- Require函数详解
- module路径解析
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 = id;
// 模块的导出对象
this.exports = {};
// 是否导出标志符
this.loaded = false;
}
// 内部的模块, 具体可以查看 process.binding的用法
NativeModule._source = process.binding('natives');
// 用于做NativeModule的内部缓存
NativeModule._cache = {};
// 这个是在module.js中使用的require函数
NativeModule.require = function(id) {
// 特殊处理,如果是native_module,直接返回
if (id == 'native_module') {
return NativeModule;
}
// 查看是否有缓存,
var cached = NativeModule.getCached(id);
if (cached) {
// 如果有,直接返回导出的对象
return cached.exports;
}
// 是否在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();
// 编译模块
nativeModule.compile();
// 导出模块的对象
return nativeModule.exports;
};
// 查找是否有缓存
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) { ',
'
});'
];
//编译
NativeModule.prototype.compile = 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 = true;
};
// 增加cache
NativeModule.prototype.cache = function() {
NativeModule._cache[this.id] = this;
};
由于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.exports;
}
// 步骤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 = replModule;
return replModule.exports;
}
debug('load native module ' + request);
return NativeModule.require(filename);
}
// 创建Module对象。
var module = new Module(filename, parent);
// 是否为主模块,
if (isMain) {
// 主模块的话,需要将当前的module赋值给process.mainModule
process.mainModule = module;
// 主模块的id特殊的赋值为"."
module.id = '.';
}
// 将创建的模块cache起来
Module._cache[filename] = module;
// 确保是否有异常
var hadException = true;
try {
// 做真正的导入模块的操作,下面会详解该函数
module.load(filename);
hadException = false;
} finally {
// 如果有异常,直接删除上述的缓存
if (hadException) {
delete Module._cache[filename];
}
}
// 返回新创建模块的exports
return module.exports;
};
// 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 = 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 = true;
};
下面是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');
try {
// 直接将模块的exports赋值为json文件的内容
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
// 异常处理
err.message = filename + ': ' + err.message;
throw err;
}
};
//node文件的打开处理,通常为C/C++文件。
Module._extensions['.node'] = process.dlopen;
下面就分析最后的一个函数_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 = this;
// 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.mainModule;
// Enable support to add extra extension types
// 将Module._extensions赋值给require
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
//将缓存也赋值给require
// require一会是函数,一会又像是对象,其实都是对象:)
require.cache = Module._cache;
// 获取当前的文件的路径
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 = require;
sandbox.exports = self.exports;
sandbox.__filename = filename;
sandbox.__dirname = dirname;
sandbox.module = self;
sandbox.global = sandbox;
sandbox.root = root;
return runInNewContext(content, sandbox, { filename: filename });
}
debug('load root module');
// root module
global.require = require;
global.exports = self.exports;
global.__filename = filename;
global.__dirname = dirname;
global.module = self;
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函数的原因