你們可能會好奇,在 Node.js 啓動後,第一個執行的 JavaScript 文件會是哪一個?它具體又會幹些什麼事?javascript
一步步來看,翻開 Node.js 的源碼,不難看出,入口文件在 src/node_main.cc
中,主要任務爲將參數傳入 node::Start
函數:java
// 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
:node
// 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 文件代碼:c++
// 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 )git
做爲第一段被執行的 JavaScript 代碼,它的歷史使命免不了就是進行一些環境和全局變量的初始化工做。代碼的總體結構很簡單,全部的初始化邏輯都被封裝在了 startup
函數中:github
// lib/internal/bootstrap_node.js 'use strict'; (function(process) { function startup() { // ... } // ... startup(); });
而在 startup
函數中,邏輯能夠分爲四塊:bootstrap
初始化全局 process
對象上的部分屬性 / 行爲緩存
初始化全局的一些 timer
方法app
初始化全局 console
等對象dom
開始執行用戶執行指定的 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); } }
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
對象的,因此在嚴格模式下給它賦值將會拋出異常,而非嚴格模式下,賦值將被忽略。
這一部分的邏輯已經在以前的文章中有所闡述,這邊就再也不重複說明啦。
仍是再次總結下:
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