人人可能会猎奇,在 Node.js 启动后,第一个实行的 JavaScript 文件会是哪一个?它细致又会干些什么事?
一步步来看,掀开 Node.js 的源码,不难看出,进口文件在 src/node_main.cc
中,重要任务为将参数传入 node::Start
函数:
// src/node_main.cc
// ...
int main(int argc, char *argv[]) {
setvbuf(stderr, NULL, _IOLBF, 1024);
return node::Start(argc, argv);
}
node::Start
函数定义于 src/node.cc
中,它举行了必要的初始化事情后,会挪用 StartNodeInstance
:
// src/node.cc
// ...
int Start(int argc, char** argv) {
// ...
NodeInstanceData instance_data(NodeInstanceType::MAIN,
uv_default_loop(),
argc,
const_cast<const char**>(argv),
exec_argc,
exec_argv,
use_debug_agent);
StartNodeInstance(&instance_data);
}
而在 StartNodeInstance
函数中,又挪用了 LoadEnvironment
函数,个中的 ExecuteString(env, MainSource(env), script_name);
步骤,便实行了第一个 JavaScript 文件代码:
// src/node.cc
// ...
void LoadEnvironment(Environment* env) {
// ...
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
// ...
}
static void StartNodeInstance(void* arg) {
// ...
{
Environment::AsyncCallbackScope callback_scope(env);
LoadEnvironment(env);
}
// ...
}
// src/node_javascript.cc
// ...
Local<String> MainSource(Environment* env) {
return String::NewFromUtf8(
env->isolate(),
reinterpret_cast<const char*>(internal_bootstrap_node_native),
NewStringType::kNormal,
sizeof(internal_bootstrap_node_native)).ToLocalChecked();
}
个中的 internal_bootstrap_node_native
,即为 lib/internal/bootstrap_node.js
中的代码。(注:许多之前的 Node.js 源码剖析文章中,所写的第一个实行的 JavaScript 文件代码为 src/node.js
,但这个文件在 Node.js v5.10 中已被移除,并被拆解为了 lib/internal/bootstrap_node.js
等其他 lib/internal
下的文件,PR 为: https://github.com/nodejs/node/pull/5103 )
正文
作为第一段被实行的 JavaScript 代码,它的历史使命免不了就是举行一些环境和全局变量的初始化事情。代码的团体构造很简朴,一切的初始化逻辑都被封装在了 startup
函数中:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
}
// ...
startup();
});
而在 startup
函数中,逻辑能够分为四块:
初始化全局
process
对象上的部份属性 / 行动初始化全局的一些
timer
要领初始化全局
console
等对象最先实行用户实行指定的 JavaScript 代码
让我们一个个来剖析。
初始化全局 process
对象上的部份属性 / 行动
增加 process
上 uncaughtException
事宜的默许行动
在 Node.js 中,假如没有为 process
上的 uncaughtException
事宜注册监听器,那末该事宜触发时,将会致使历程退出,这个行动就是在 startup
函数里增加的:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
setupProcessFatal();
}
// ...
function setupProcessFatal() {
process._fatalException = function(er) {
var caught;
// ...
if (!caught)
caught = process.emit('uncaughtException', er);
if (!caught) {
try {
if (!process._exiting) {
process._exiting = true;
process.emit('exit', 1);
}
} catch (er) {
}
}
// ...
return caught;
};
}
});
逻辑非常直白,运用到了 EventEmitter#emit
的返回值来推断该事宜上是不是有注册过的监听器,并终究挪用 c++ 的 exit()
函数退出历程:
// src/node.cc
// ...
void FatalException(Isolate* isolate,
Local<Value> error,
Local<Message> message) {
// ...
Local<Value> caught =
fatal_exception_function->Call(process_object, 1, &error);
// ...
if (false == caught->BooleanValue()) {
ReportException(env, error, message);
exit(1);
}
}
依据 Node.js 在启动时所带的某些参数,来调解 process
上 warning
事宜触发时的行动
细致来讲,这些参数是:--no-warnings
,--no-deprecation
,--trace-deprecation
和 --throw-deprecation
。这些参数的有没有信息,会先被挂载在 process
对象上:
// src/node.cc
// ...
if (no_deprecation) {
READONLY_PROPERTY(process, "noDeprecation", True(env->isolate()));
}
if (no_process_warnings) {
READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate()));
}
if (trace_warnings) {
READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate()));
}
if (throw_deprecation) {
READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate()));
}
然后依据这些信息,掌握行动:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
NativeModule.require('internal/process/warning').setup();
}
// ...
startup();
});
// lib/internal/process/warning.js
'use strict';
const traceWarnings = process.traceProcessWarnings;
const noDeprecation = process.noDeprecation;
const traceDeprecation = process.traceDeprecation;
const throwDeprecation = process.throwDeprecation;
const prefix = `(${process.release.name}:${process.pid}) `;
exports.setup = setupProcessWarnings;
function setupProcessWarnings() {
if (!process.noProcessWarnings) {
process.on('warning', (warning) => {
if (!(warning instanceof Error)) return;
const isDeprecation = warning.name === 'DeprecationWarning';
if (isDeprecation && noDeprecation) return;
const trace = traceWarnings || (isDeprecation && traceDeprecation);
if (trace && warning.stack) {
console.error(`${prefix}${warning.stack}`);
} else {
var toString = warning.toString;
if (typeof toString !== 'function')
toString = Error.prototype.toString;
console.error(`${prefix}${toString.apply(warning)}`);
}
});
}
// ...
}
细致行动的话,文档中已经有细致申明,逻辑总结来讲,就是按需将正告打印到掌握台,或许按需抛出特定的非常。个中 NativeModule
对象为 Node.js 在当前的函数体的部分作用域内,完成的一个最小可用的模块加载器,具有缓存等基本功用。
为 process
增加上 stdin
, stdout
和 stderr
属性
一般为 tty.ReadStream
类和 tty.WriteStream
类的实例:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
NativeModule.require('internal/process/stdio').setup();
}
// ...
startup();
});
// lib/internal/process/stdio.js
// ...
function setupStdio() {
var stdin, stdout, stderr;
process.__defineGetter__('stdout', function() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
// ...
return stdout
}
process.__defineGetter__('stderr', function() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
// ...
return stderr;
});
process.__defineGetter__('stdin', function() {
if (stdin) return stdin;
var tty_wrap = process.binding('tty_wrap');
var fd = 0;
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
var tty = require('tty');
stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
readable: true,
writable: false
});
break;
// ...
}
return stdin;
}
}
function createWritableStdioStream(fd) {
var stream;
var tty_wrap = process.binding('tty_wrap');
// Note stream._type is used for test-module-load-list.js
switch (tty_wrap.guessHandleType(fd)) {
case 'TTY':
var tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
// ...
}
// ...
}
为 process
增加上 nextTick
要领
细致的做法就是将注册的回调推动行列中,守候事宜轮回的下一次 Tick ,一个个掏出实行:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
NativeModule.require('internal/process/next_tick').setup();
}
// ...
startup();
});
// lib/internal/process/next_tick.js
'use strict';
exports.setup = setupNextTick;
function setupNextTick() {
var nextTickQueue = [];
// ...
var kIndex = 0;
var kLength = 1;
process.nextTick = nextTick;
process._tickCallback = _tickCallback;
function _tickCallback() {
var callback, args, tock;
do {
while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback;
args = tock.args;
_combinedTickCallback(args, callback);
if (1e4 < tickInfo[kIndex])
tickDone();
}
tickDone();
} while (tickInfo[kLength] !== 0);
}
function nextTick(callback) {
if (typeof callback !== 'function')
throw new TypeError('callback is not a function');
if (process._exiting)
return;
var args;
if (arguments.length > 1) {
args = new Array(arguments.length - 1);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
}
nextTickQueue.push(new TickObject(callback, args));
tickInfo[kLength]++;
}
}
// ...
为 process
增加上 hrtime
, kill
, exit
要领
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
_process.setup_hrtime();
_process.setupKillAndExit();
}
// ...
startup();
});
这些功用的中心完成也重度依赖于 c++ 函数:
hrtime
要领依赖于libuv
供应的uv_hrtime()
函数kill
要领依赖于libuv
供应的uv_kill(pid, sig)
函数exit
要领依赖于 c++ 供应的exit(code)
函数
初始化全局的一些 timer
要领和 console
等对象
这些初始化都干的非常简朴,直接赋值:
// lib/internal/bootstrap_node.js
'use strict';
(function(process) {
function startup() {
// ...
setupGlobalVariables();
if (!process._noBrowserGlobals) {
setupGlobalTimeouts();
setupGlobalConsole();
}
function setupGlobalVariables() {
global.process = process;
// ...
global.Buffer = NativeModule.require('buffer').Buffer;
process.domain = null;
process._exiting = false;
}
function setupGlobalTimeouts() {
const timers = NativeModule.require('timers');
global.clearImmediate = timers.clearImmediate;
global.clearInterval = timers.clearInterval;
global.clearTimeout = timers.clearTimeout;
global.setImmediate = timers.setImmediate;
global.setInterval = timers.setInterval;
global.setTimeout = timers.setTimeout;
}
function setupGlobalConsole() {
global.__defineGetter__('console', function() {
return NativeModule.require('console');
});
}
}
// ...
startup();
});
值得注意的一点是,因为 console
是经由过程 __defineGetter__
赋值给 global
对象的,所以在严厉形式下给它赋值将会抛出非常,而非严厉形式下,赋值将被疏忽。
最先实行用户实行指定的 JavaScript 代码
这一部份的逻辑已经在之前的文章中有所论述,这边就不再反复申明啦。
末了
照样再次总结下:
lib/internal/bootstrap_node.js
中的代码 为 Node.js 实行后第一段被实行的 JavaScript 代码,从src/node.cc
中的node::LoadEnvironment
被挪用lib/internal/bootstrap_node.js
重要举行了一些初始化事情:初始化全局
process
对象上的部份属性 / 行动增加接收到
uncaughtException
事宜时的默许行动依据 Node.js 启动时参数,调解
warning
事宜的行动增加上
stdin
,stdout
和stderr
属性增加上
nextTick
,hrtime
,exit
要领
初始化全局的一些
timer
要领初始化全局
console
等对象最先实行用户实行指定的 JavaScript 代码
参考
https://github.com/nodejs/node/blob/master/src/node_javascript.cc
https://github.com/nodejs/node/blob/master/lib/internal/process.js
https://github.com/nodejs/node/blob/master/lib/internal/process/next_tick.js
https://github.com/nodejs/node/blob/master/lib/internal/process/stdio.js
https://github.com/nodejs/node/blob/master/lib/internal/process/warning.js
https://github.com/nodejs/node/blob/master/lib/internal/bootstrap_node.js