node.js脚本文件是node.js的js部分的入口。它里面有几个重要的对象:
1.NativeModule:
Nodejs的模块分为两部分,一部分是用C实现的模块,比如:buffer、stdio等,另一部分是用js实现的模块比如net、dns等。对于C的模块,nodejs通过node.h提供的NODE_MODULE方法将模块存储在变量_module里。在srcde_extensions.cc中提供了get_builtin_module(name)接口获取这些模块;而对于js的模块,nodejs在srcde.js中实现了一个NativeModule对象用于管理js模块,它通过调用process.binding(“natives”)把所有内置的js模块放在NativeModule._source上,并提供require接口供调用。
为了提高模块加载的效率,nodejs在binding函数和require函数中都增加了缓存机制,在首次加载模块的时候,模块会copy的缓存中,以后的模块加载实际上只是从缓存中获得模块的接口,这样的机制使得nodejs在模块加载方面十分高效。
Nodejs模块的调用形式如下:
加载C++模块(以stdio为例):
process.binding("stdio")->get_builtin_module("stdio")-> _module -> NODE_MODULE(node_stdio, node::Stdio::Initialize)(定义)
加载js模块(以net为例)
require("net") -> NativeModule.require("net") -> process.binding("natives")["net"] -> DefineJavaScript() -> natives[] -> node_natives.h
function NativeModule(id) {
this.filename = id + '.js';
this.id = id;
this.exports = {};
this.loaded = false;
}
NativeModule._source = process.binding('natives');// 加载内部模块
NativeModule._cache = {};
// 主要就是这个方法
NativeModule.require = function(id) {
if (id == 'native_module') {
return NativeModule;
}
var cached = NativeModule.getCached(id);
if (cached) {
return cached.exports; // 如果存在缓存,直接返回
}
if (!NativeModule.exists(id)) {
throw new Error('No such native module ' + id); // ID错误
}
process.moduleLoadList.push('NativeModule ' + id);
var nativeModule = new NativeModule(id); // 如果不存在缓存,就创建一个
nativeModule.cache(); // 缓存起来
nativeModule.compile(); // 加载
return nativeModule.exports;
};
NativeModule.getCached = function(id) {
return NativeModule._cache[id];
}
NativeModule.exists = function(id) {
return NativeModule._source.hasOwnProperty(id);
}
NativeModule.getSource = function(id) {
return NativeModule._source[id];
}
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
NativeModule.prototype.compile = function() {
var source = NativeModule.getSource(this.id);
source = NativeModule.wrap(source);
var fn = runInThisContext(source, this.filename, true);
fn(this.exports, NativeModule.require, this, this.filename);
this.loaded = true;
};
NativeModule.prototype.cache = function() {
NativeModule._cache[this.id] = this;
};
2.startup:
(function(process) {
this.global = this;
startup();
});
整个node.js文件就是一个函数对象的定义,而这个函数只做了两件事,所有到逻辑都在startup里面。
function startup() {
var EventEmitter = NativeModule.require('events').EventEmitter;
process.__proto__ = Object.create(EventEmitter.prototype, {
constructor: {
value: process.constructor
}
});
EventEmitter.call(process);
process.EventEmitter = EventEmitter; // process.EventEmitter is deprecated 已经过时
// do this good and early, since it handles errors.
startup.processFatal(); // 异常处理模块
startup.globalVariables(); // 全局属性初始化
startup.globalTimeouts(); // 定义全局时间方法
startup.globalConsole(); // 加载控制台模块
startup.processAssert(); // 定义断言对象
startup.processConfig(); // 加载process.config里面的配置内容,保存做process.config中
startup.processNextTick(); // 定义process.nextTick方法,这个方法是让一个行为在下一次事件轮循的时候执行。
startup.processStdio(); // 标准流
startup.processKillAndExit(); // 定义进程到kill和exit方法
startup.processSignalHandlers(); // 定义为进程添加信号的方法,这里的信号是指事件
startup.processChannel(); // 这个暂时不清楚
startup.resolveArgv0();
// There are various modes that Node can run in. The most common two
// are running from a script and running the REPL - but there are a few
// others like the debugger or running --eval arguments. Here we decide
// which mode we run in.
if (NativeModule.exists('_third_party_main')) {
// To allow people to extend Node in different ways, this hook allows
// one to drop a file lib/_third_party_main.js into the build
// directory which will be executed instead of Node's normal loading.
process.nextTick(function() {
NativeModule.require('_third_party_main');
});
} else if (process.argv[1] == 'debug') {
// Start the debugger agent
var d = NativeModule.require('_debugger');
d.start();
} else if (process._eval != null) {
// User passed '-e' or '--eval' arguments to Node.
evalScript('[eval]');
} else if (process.argv[1]) { // 有第一个参数到时候
// make process.argv[1] into a full path
var path = NativeModule.require('path');
process.argv[1] = path.resolve(process.argv[1]);
// If this is a worker in cluster mode, start up the communiction
// channel.
if (process.env.NODE_UNIQUE_ID) {
var cluster = NativeModule.require('cluster');
cluster._setupWorker();
// Make sure it's not accidentally inherited by child processes.
delete process.env.NODE_UNIQUE_ID;
}
var Module = NativeModule.require('module');
if (global.v8debug &&
process.execArgv.some(function(arg) {
return arg.match(/^--debug-brk(=[0-9]*)?$/);
})) {
// XXX Fix this terrible hack!
//
// Give the client program a few ticks to connect.
// Otherwise, there's a race condition where `node debug foo.js`
// will not be able to connect in time to catch the first
// breakpoint message on line 1.
//
// A better fix would be to somehow get a message from the
// global.v8debug object about a connection, and runMain when
// that occurs. --isaacs
var debugTimeout = +process.env.NODE_DEBUG_TIMEOUT || 50;
setTimeout(Module.runMain, debugTimeout);
} else {
// Main entry point into most programs:
Module.runMain();
}
} else { // 没有参数的时候
var Module = NativeModule.require('module');
// If -i or --interactive were passed, or stdin is a TTY.
if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
// REPL
var opts = {
useGlobal: true,
ignoreUndefined: false
};
if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
opts.terminal = false;
}
if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
opts.useColors = false;
}
var repl = Module.requireRepl().start(opts);
repl.on('exit', function() {
process.exit();
});
} else {
// Read all of stdin - execute it.
process.stdin.setEncoding('utf8');
var code = '';
process.stdin.on('data', function(d) {
code += d;
});
process.stdin.on('end', function() {
process._eval = code;
evalScript('[stdin]');
});
}
}
startup.globalVariables = function() { // 这个方法很蛋疼啊,定义这么多重复变量吃啊
global.process = process;
global.global = global;
global.GLOBAL = global;
global.root = global;
global.Buffer = NativeModule.require('buffer').Buffer;
process.binding('buffer').setFastBufferConstructor(global.Buffer);
process.domain = null;
process._exiting = false;
};
startup.globalTimeouts = function() {
global.setTimeout = function() {
var t = NativeModule.require('timers');
return t.setTimeout.apply(this, arguments);
};
global.setInterval = function() {
var t = NativeModule.require('timers');
return t.setInterval.apply(this, arguments);
};
global.clearTimeout = function() {
var t = NativeModule.require('timers');
return t.clearTimeout.apply(this, arguments);
};
global.clearInterval = function() {
var t = NativeModule.require('timers');
return t.clearInterval.apply(this, arguments);
};
global.setImmediate = function() {
var t = NativeModule.require('timers');
return t.setImmediate.apply(this, arguments);
};
global.clearImmediate = function() {
var t = NativeModule.require('timers');
return t.clearImmediate.apply(this, arguments);
};
};
}
执行脚本部分代码:
function evalScript(name) {
var Module = NativeModule.require('module');
var path = NativeModule.require('path');
var cwd = process.cwd();
var module = new Module(name);
module.filename = path.join(cwd, name);
module.paths = Module._nodeModulePaths(cwd);
var script = process._eval;
if (!Module._contextLoad) {
var body = script;
script = 'global.__filename = ' + JSON.stringify(name) + ';\n' +
'global.exports = exports;\n' +
'global.module = module;\n' +
'global.__dirname = __dirname;\n' +
'global.require = require;\n' +
'return require("vm").runInThisContext(' +
JSON.stringify(body) + ', ' +
JSON.stringify(name) + ', true);\n';
}
var result = module._compile(script, name + '-wrapper');
if (process._print_eval) console.log(result);
}
nodejs执行脚本的方式有两种,一种是process进程对象的._compile方法,上面已经给出了。另外一种是evals模块的Script对象。module._compile(script, name + '-wrapper');。Script = process.binding('evals').Script,Script.runInThisContext(),Script.runInNewContext()。他们的本质是一样的,都是把内容传给V8解析执行。
var NativeModule = require('native_module');
var Script = process.binding('evals').NodeScript;
var runInThisContext = Script.runInThisContext;
var runInNewContext = Script.runInNewContext;