nodejs源码—初始化
概述
相信很多的人,每天在終端不止一遍的執(zhí)行著node這條命令,對(duì)于很多人來(lái)說(shuō),它就像一個(gè)黑盒,并不知道背后到底發(fā)生了什么,本文將會(huì)為大家揭開(kāi)這個(gè)神秘的面紗,由于本人水平有限,所以只是講一個(gè)大概其,主要關(guān)注的過(guò)程就是node模塊的初始化,event loop和v8的部分基本沒(méi)有深入,這些部分可以關(guān)注一下我以后的文章。(提示本文非常的長(zhǎng),希望大家不要看煩~) 本文node基于10.5.0
node是什么?
這個(gè)問(wèn)題很多人都會(huì)回答就是v8 + libuv,但是除了這個(gè)兩個(gè)庫(kù)以外node還依賴許多優(yōu)秀的開(kāi)源庫(kù),可以通過(guò)process.versions來(lái)看一下:
- http_parser主要用于解析http數(shù)據(jù)包的模塊,在這個(gè)庫(kù)的作者也是ry,一個(gè)純c的庫(kù),無(wú)任何依賴
- v8這個(gè)大家就非常熟悉了,一個(gè)優(yōu)秀的js引擎
- uv這個(gè)就是ry實(shí)現(xiàn)的libuv,其封裝了libev和IOCP,實(shí)現(xiàn)了跨平臺(tái),node中的i/o就是它,盡管js是單線程的,但是libuv并不是,其有一個(gè)線程池來(lái)處理這些i/o操作。
- zlib主要來(lái)處理壓縮操作,諸如熟悉的gzip操作
- ares是c-ares,這個(gè)庫(kù)主要用于解析dns,其也是異步的
- modules就是node的模塊系統(tǒng),其遵循的規(guī)范為commonjs,不過(guò)node也支持了ES模塊,不過(guò)需要加上參數(shù)并且文件名后綴需要為mjs,通過(guò)源碼看,node將ES模塊的名稱(chēng)作為了一種url來(lái)看待,具體可以參見(jiàn)這里
- nghttp2如其名字一樣,是一個(gè)http2的庫(kù)
- napi是在node8出現(xiàn),node10穩(wěn)定下來(lái)的,可以給編寫(xiě)node原生模塊更好的體驗(yàn)(終于不用在依賴于nan,每次更換node版本還要重新編譯一次了)
- openssl非常著名的庫(kù),tls模塊依賴于這個(gè)庫(kù),當(dāng)然還包括https
- icu就是small-icu,主要用于解決跨平臺(tái)的編碼問(wèn)題,versions對(duì)象中的unicode,cldr,tz也源自icu,這個(gè)的定義可以參見(jiàn)這里
從這里可以看出的是process對(duì)象在node中非常的重要,個(gè)人的理解,其實(shí)node與瀏覽器端最主要的區(qū)別,就在于這個(gè)process對(duì)象
注:node只是用v8來(lái)進(jìn)行js的解析,所以不一定非要依賴v8,也可以用其他的引擎來(lái)代替,比如利用微軟的ChakraCore,對(duì)應(yīng)的node倉(cāng)庫(kù)
node初始化
經(jīng)過(guò)上面的一通分析,對(duì)node的所有依賴有了一定的了解,下面來(lái)進(jìn)入正題,看一下node的初始化過(guò)程:
挖坑
node_main.cc為入口文件,可以看到的是除了調(diào)用了node::Start之外,還做了兩件事情:
NODE_SHARED_MODE忽略SIGPIPE信號(hào)
SIGPIPE信號(hào)出現(xiàn)的情況一般在socket收到RST packet之后,扔向這個(gè)socket寫(xiě)數(shù)據(jù)時(shí)產(chǎn)生,簡(jiǎn)單來(lái)說(shuō)就是client想server發(fā)請(qǐng)求,但是這時(shí)候client已經(jīng)掛掉,這時(shí)候就會(huì)產(chǎn)生SIGPIPE信號(hào),產(chǎn)生這個(gè)信號(hào)會(huì)使server端掛掉,其實(shí)node::PlatformInit中也做了這種操作,不過(guò)只是針對(duì)non-shared lib build
改變緩沖行為
stdout的默認(rèn)緩沖行為為_(kāi)IOLBF(行緩沖),但是對(duì)于這種來(lái)說(shuō)交互性會(huì)非常的差,所以將其改為_(kāi)IONBF(不緩沖)
探索
node.cc文件中總共有三個(gè)Start函數(shù),先從node_main.cc中掉的這個(gè)Start函數(shù)開(kāi)始看:
int Start(int argc, char** argv) {// 退出之前終止libuv的終端行為,為正常退出的情況atexit([] () { uv_tty_reset_mode(); });// 針對(duì)平臺(tái)進(jìn)行初始化PlatformInit();// ...Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);// ...v8_platform.Initialize(v8_thread_pool_size);// 熟悉的v8初始化函數(shù)V8::Initialize();// ..const int exit_code =Start(uv_default_loop(), argc, argv, exec_argc, exec_argv); } 復(fù)制代碼上面函數(shù)只保留了一些關(guān)鍵不走,先來(lái)看看PlatformInit
PlatfromInit
unix中將一切都看作文件,進(jìn)程啟動(dòng)時(shí)會(huì)默認(rèn)打開(kāi)三個(gè)i/o設(shè)備文件,也就是stdin stdout stderr,默認(rèn)會(huì)分配0 1 2三個(gè)描述符出去,對(duì)應(yīng)的文件描述符常量為STDIN_FILENO STDOUT_FILENO STDERR_FILENO,而windows中沒(méi)有文件描述符的這個(gè)概念,對(duì)應(yīng)的是句柄,PlatformInit首先是檢查是否將這個(gè)三個(gè)文件描述符已經(jīng)分配出去,若沒(méi)有,則利用open("/dev/null", O_RDWR)分配出去,對(duì)于windows做了同樣的操作,分配句柄出去,而且windows只做了這一個(gè)操作;對(duì)于unix來(lái)說(shuō)還會(huì)針對(duì)SIGINT(用戶調(diào)用Ctrl-C時(shí)發(fā)出)和SIGTERM(SIGTERM與SIGKILL類(lèi)似,但是不同的是該信號(hào)可以被阻塞和處理,要求程序自己退出)信號(hào)來(lái)做一些特殊處理,這個(gè)處理與正常退出時(shí)一樣;另一個(gè)重要的事情就是下面這段代碼:
struct rlimit lim;// soft limit 不等于 hard limit, 意味著可以增加if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) {// Do a binary search for the limit.rlim_t min = lim.rlim_cur;rlim_t max = 1 << 20;// But if there's a defined upper bound, don't search, just set it.if (lim.rlim_max != RLIM_INFINITY) {min = lim.rlim_max;max = lim.rlim_max;}do {lim.rlim_cur = min + (max - min) / 2;// 對(duì)于mac來(lái)說(shuō) hard limit 為unlimited// 但是內(nèi)核有限制最大的文件描述符,超過(guò)這個(gè)限制則設(shè)置失敗if (setrlimit(RLIMIT_NOFILE, &lim)) {max = lim.rlim_cur;} else {min = lim.rlim_cur;}} while (min + 1 < max);} 復(fù)制代碼這個(gè)件事情也就是提高一個(gè)進(jìn)程允許打開(kāi)的最大文件描述符,但是在mac上非常的奇怪,執(zhí)行ulimit -H -n得到hard limit是unlimited,所以我認(rèn)為mac上的最大文件描述符會(huì)被設(shè)置為1 << 20,但是最后經(jīng)過(guò)實(shí)驗(yàn)發(fā)現(xiàn)最大只能為24576,非常的詭異,最后經(jīng)過(guò)一頓搜索,查到了原來(lái)mac的內(nèi)核對(duì)能打開(kāi)的文件描述符也有限制,可以用sysctl -A | grep kern.maxfiles進(jìn)行查看,果然這個(gè)數(shù)字就是24576
Init
Init函數(shù)調(diào)用了RegisterBuiltinModules:
// node.cc void RegisterBuiltinModules() { #define V(modname) _register_##modname();NODE_BUILTIN_MODULES(V) #undef V }// node_internals.h #define NODE_BUILTIN_MODULES(V) NODE_BUILTIN_STANDARD_MODULES(V) NODE_BUILTIN_OPENSSL_MODULES(V) NODE_BUILTIN_ICU_MODULES(V) 復(fù)制代碼從名字也可以看出上面的過(guò)程是進(jìn)行c++模塊的初始化,node利用了一些宏定義的方式,主要關(guān)注NODE_BUILTIN_STANDARD_MODULES這個(gè)宏:
#define NODE_BUILTIN_STANDARD_MODULES(V)V(async_wrap) V(buffer)... 復(fù)制代碼結(jié)合上面的定義,可以得出編譯后的代碼大概為:
void RegisterBuiltinModules() {_register_async_wrap();_register_buffer(); } 復(fù)制代碼而這些_register又是從哪里來(lái)的呢?以buffer來(lái)說(shuō),對(duì)應(yīng)c++文件為src/node_buffer.cc,來(lái)看這個(gè)文件的最后一行,第二個(gè)參數(shù)是模塊的初始化函數(shù):
NODE_BUILTIN_MODULE_CONTEXT_AWARE(buffer, node::Buffer::Initialize) 復(fù)制代碼這個(gè)宏存在于node_internals.h中:
#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags)static node::node_module _module = {NODE_MODULE_VERSION, flags, nullptr, __FILE__, nullptr, (node::addon_context_register_func) (regfunc),// 暴露給js使用的模塊的初始化函數(shù)NODE_STRINGIFY(modname), priv, nullptr }; void _register_ ## modname() { node_module_register(&_module); }#define NODE_BUILTIN_MODULE_CONTEXT_AWARE(modname, regfunc) NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_BUILTIN) 復(fù)制代碼發(fā)現(xiàn)調(diào)用的_register_buffer實(shí)質(zhì)上調(diào)用的是node_module_register(&_module),每一個(gè)c++模塊對(duì)應(yīng)的為一個(gè)node_module結(jié)構(gòu)體,再來(lái)看看node_module_register發(fā)生了什么:
extern "C" void node_module_register(void* m) {struct node_module* mp = reinterpret_cast<struct node_module*>(m);if (mp->nm_flags & NM_F_BUILTIN) {mp->nm_link = modlist_builtin;modlist_builtin = mp;}... } 復(fù)制代碼由此可以見(jiàn),c++模塊被存儲(chǔ)在了一個(gè)鏈表中,后面process.binding()本質(zhì)上就是在這個(gè)鏈表中查找對(duì)應(yīng)c++模塊,node_module是鏈表中的一個(gè)節(jié)點(diǎn),除此之外Init還初始化了一些變量,這些變量基本上都是取決于環(huán)境變量用getenv獲得即可
v8初始化
到執(zhí)行完Init為止,還沒(méi)有涉及的js與c++的交互,在將一些環(huán)境初始化之后,就要開(kāi)始用v8這個(gè)大殺器了,v8_platform是一個(gè)結(jié)構(gòu)體,可以理解為是node對(duì)于v8的v8::platform一個(gè)封裝,緊接著的就是對(duì)v8進(jìn)行初始化,自此開(kāi)始具備了與js進(jìn)行交互的能力,初始化v8之后,創(chuàng)建了一個(gè)libuv事件循環(huán)就進(jìn)入了下一個(gè)Start函數(shù)
第二個(gè)Start函數(shù)
inline int Start(uv_loop_t* event_loop,int argc, const char* const* argv,int exec_argc, const char* const* exec_argv) {std::unique_ptr<ArrayBufferAllocator, decltype(&FreeArrayBufferAllocator)>allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator);Isolate* const isolate = NewIsolate(allocator.get());// ...{Locker locker(isolate);Isolate::Scope isolate_scope(isolate);HandleScope handle_scope(isolate);} } 復(fù)制代碼首先創(chuàng)建了一個(gè)v8的Isolate(隔離),隔離在v8中非常常見(jiàn),仿佛和進(jìn)程一樣,不同隔離不共享資源,有著自己得堆棧,但是正是因?yàn)檫@個(gè)原因在多線程的情況下,要是對(duì)每一個(gè)線程都創(chuàng)建一個(gè)隔離的話,那么開(kāi)銷(xiāo)會(huì)非常的大(可喜可賀的是node有了worker_threads),這時(shí)候可以借助Locker來(lái)進(jìn)行同步,同時(shí)也保證了一個(gè)Isolate同一時(shí)刻只能被一個(gè)線程使用;下面兩行就是v8的常規(guī)套路,下一步一般就是創(chuàng)建一個(gè)Context(最簡(jiǎn)化的一個(gè)流程可以參見(jiàn)v8的hello world),HandleScope叫做句柄作用域,一般都是放在函數(shù)的開(kāi)頭,來(lái)管理函數(shù)創(chuàng)建的一些句柄(水平有限,暫時(shí)不深究,先挖個(gè)坑);第二個(gè)Start的主要流程就是這個(gè),下面就會(huì)進(jìn)入最后一個(gè)Start函數(shù),這個(gè)函數(shù)可以說(shuō)是非常的關(guān)鍵,會(huì)揭開(kāi)所有的謎題
解開(kāi)謎題
inline int Start(Isolate* isolate, IsolateData* isolate_data,int argc, const char* const* argv,int exec_argc, const char* const* exec_argv) {HandleScope handle_scope(isolate);// 常規(guī)套路Local<Context> context = NewContext(isolate);Context::Scope context_scope(context);Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling);// ... 復(fù)制代碼可以見(jiàn)到v8的常見(jiàn)套路,創(chuàng)建了一個(gè)上下文,這個(gè)上下文就是js的執(zhí)行環(huán)境,Context::Scope是用來(lái)管理這個(gè)Context,Environment可以理解為一個(gè)node的運(yùn)行環(huán)境,記錄了isolate,event loop等,Start的過(guò)程主要是做了一些libuv的初始化以及process對(duì)象的定義:
auto process_template = FunctionTemplate::New(isolate());process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));auto process_object =process_template->GetFunction()->NewInstance(context()).ToLocalChecked();set_process_object(process_object);SetupProcessObject(this, argc, argv, exec_argc, exec_argv); 復(fù)制代碼SetupProcessObject生成了一個(gè)c++層面上的process對(duì)象,這個(gè)已經(jīng)基本上和平時(shí)node中的process對(duì)象一致,但是還會(huì)有一些出入,比如沒(méi)有binding等,完成了這個(gè)過(guò)程之后就開(kāi)始了LoadEnvironment
LoadEnvironment
Local<String> loaders_name =FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js"); MaybeLocal<Function> loaders_bootstrapper =GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name); Local<String> node_name =FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js"); MaybeLocal<Function> node_bootstrapper =GetBootstrapper(env, NodeBootstrapperSource(env), node_name); 復(fù)制代碼先將lib/internal/bootstrap文件夾下的兩個(gè)文件讀進(jìn)來(lái),然后利用GetBootstrapper來(lái)執(zhí)行js代碼分別得到了一個(gè)函數(shù),一步步來(lái)看,先看看GetBootstrapper為什么可以執(zhí)行js代碼,查看這個(gè)函數(shù)可以發(fā)現(xiàn)主要是因?yàn)镋xecuteString:
MaybeLocal<v8::Script> script =v8::Script::Compile(env->context(), source, &origin); ... MaybeLocal<Value> result = script.ToLocalChecked()->Run(env->context()); 復(fù)制代碼這個(gè)主要利用了v8的能力,對(duì)js文件進(jìn)行了解析和執(zhí)行,打開(kāi)loaders.js看看其參數(shù),需要五個(gè),撿兩個(gè)最重要的來(lái)說(shuō),分別是process和getBinding,這里面往后繼續(xù)看LoadEnvironment發(fā)現(xiàn)process對(duì)象就是剛剛生成的,而getBinding是函數(shù)GetBinding:
node_module* mod = get_builtin_module(*module_v); Local<Object> exports; if (mod != nullptr) {exports = InitModule(env, mod, module); } else if (!strcmp(*module_v, "constants")) {exports = Object::New(env->isolate());CHECK(exports->SetPrototype(env->context(),Null(env->isolate())).FromJust());DefineConstants(env->isolate(), exports); } else if (!strcmp(*module_v, "natives")) { // NativeModule _sourceexports = Object::New(env->isolate());DefineJavaScript(env, exports); } else {return ThrowIfNoSuchModule(env, *module_v); }args.GetReturnValue().Set(exports); 復(fù)制代碼其作用就是根據(jù)傳參來(lái)初始化指定的模塊,當(dāng)然也有比較特殊的兩個(gè)分別是constants和natives(后面再看),get_builtin_module調(diào)用的就是FindModule,還記得之前在Init過(guò)程中將模塊都注冊(cè)到的鏈表嗎?FindModule就是遍歷這個(gè)鏈表找到相應(yīng)的模塊:
struct node_module* mp; for (mp = list; mp != nullptr; mp = mp->nm_link) {if (strcmp(mp->nm_modname, name) == 0)break; } 復(fù)制代碼InitModule就是調(diào)用之前注冊(cè)模塊定義的初始化函數(shù),還以buffer看的話,就是執(zhí)行node::Buffer::Initialize函數(shù),打開(kāi)著函數(shù)來(lái)看和平時(shí)寫(xiě)addon的方式一樣,也會(huì)暴露一個(gè)對(duì)象出來(lái)供js調(diào)用;LoadEnvironment下面就是將process, GetBinding等作為傳入傳給上面生成好的函數(shù)并且利用v8來(lái)執(zhí)行,來(lái)到了大家熟悉的領(lǐng)域,來(lái)看看loaders.js:
const moduleLoadList = []; ObjectDefineProperty(process, 'moduleLoadList', {value: moduleLoadList,configurable: true,enumerable: true,writable: false }); 復(fù)制代碼定義了一個(gè)已經(jīng)加載的Module的數(shù)組,也可以在node通過(guò)process.moduleLoadList來(lái)看看加載了多少的原生模塊進(jìn)來(lái)
process.binding
process.binding = function binding(module) {module = String(module);let mod = bindingObj[module];if (typeof mod !== 'object') {mod = bindingObj[module] = getBinding(module);moduleLoadList.push(`Binding ${module}`);}return mod; }; 復(fù)制代碼終于到了這個(gè)方法,翻看lib中的js文件,有著非常多的這種調(diào)用,這個(gè)函數(shù)就是對(duì)GetBinding做了一個(gè)js層面的封裝,做的無(wú)非是查看一下這個(gè)模塊是否已經(jīng)加載完成了,是的話直接返回回去,不需要再次初始化了,所以利用prcoess.binding加載了對(duì)應(yīng)的c++模塊(可以執(zhí)行一下process.binding('buffer'),然后再去node_buffer.cc中看看)繼續(xù)向下看,會(huì)發(fā)現(xiàn)定義了一個(gè)class就是NativeModule,發(fā)現(xiàn)其有一個(gè)靜態(tài)屬性:
加載js
NativeModule._source = getBinding('natives'); 復(fù)制代碼返回到GetBinding函數(shù),看到的是一個(gè)if分支就是這種情況:
exports = Object::New(env->isolate()); DefineJavaScript(env, exports); 復(fù)制代碼來(lái)看看DefineJavaScript發(fā)生了什么樣的事情,這個(gè)函數(shù)發(fā)現(xiàn)只能在頭文件(node_javascript.h)里面找到,但是根本找不到具體的實(shí)現(xiàn),這是個(gè)什么鬼???去翻一下node.gyp文件發(fā)現(xiàn)這個(gè)文件是用js2c.py這個(gè)文件生成的,去看一下這個(gè)python文件,可以發(fā)現(xiàn)許多的代碼模板,每一個(gè)模板都是用Render返回的,data參數(shù)就是js文件的內(nèi)容,最終會(huì)被轉(zhuǎn)換為c++中的byte數(shù)組,同時(shí)定義了一個(gè)將其轉(zhuǎn)換為字符串的方法,那么問(wèn)題來(lái)了,這些文件都是那些呢?答案還是在node.gyp中,就是library_files數(shù)組,發(fā)現(xiàn)包含了lib下的所有的文件和一些dep下的js文件,DefineJavaScript這個(gè)文件做的就是將待執(zhí)行的js代碼注冊(cè)下,所以NativeModule._source中存儲(chǔ)的是一些待執(zhí)行的js代碼,來(lái)看一下NativeModule.require:
NativeModule
const cached = NativeModule.getCached(id); if (cached && (cached.loaded || cached.loading)) {return cached.exports; } moduleLoadList.push(`NativeModule ${id}`);const nativeModule = new NativeModule(id);nativeModule.cache(); nativeModule.compile();return nativeModule.exports; 復(fù)制代碼可以發(fā)現(xiàn)NativeModule也有著緩存的策略,require先把其放到_cache中再次require就不會(huì)像第一次那樣執(zhí)行這個(gè)模塊,而是直接用緩存中執(zhí)行好的,后面說(shuō)的Module與其同理,看一下compile的實(shí)現(xiàn):
let source = NativeModule.getSource(this.id); source = NativeModule.wrap(source);NativeModule.wrap = function(script) {return NativeModule.wrapper[0] + script + NativeModule.wrapper[1]; }; NativeModule.wrapper = ['(function (exports, require, module, process) {','\n});' ]; 復(fù)制代碼首先從_source中取出相應(yīng)的模塊,然后對(duì)這個(gè)模塊進(jìn)行包裹成一個(gè)函數(shù),執(zhí)行函數(shù)用的是什么呢?
const script = new ContextifyScript(source, this.filename, 0, 0,codeCache[this.id], false, undefined );this.script = script; const fn = script.runInThisContext(-1, true, false); const requireFn = this.id.startsWith('internal/deps/') ?NativeModule.requireForDeps :NativeModule.require; fn(this.exports, requireFn, this, process); 復(fù)制代碼本質(zhì)上就是調(diào)用了vm編譯自婦產(chǎn)得到函數(shù),然后給其傳入了一些參數(shù)并執(zhí)行,this.exports就是一個(gè)對(duì)象,require區(qū)分了一下是否加載node依賴的js文件,this也就是參數(shù)module,這也說(shuō)明了兩者的關(guān)系,exports就是module的一個(gè)屬性,也解釋了為什么exports.xx之后再指定module.exports = yy會(huì)將xx忽略掉,還記得LoadEnvironment嗎?bootstrap/loaders.js執(zhí)行完之后執(zhí)行了bootstrap/node.js,可以說(shuō)這個(gè)文件是node真正的入口,比如定義了global對(duì)象上的屬性,比如console setTimeout等,由于篇幅有限,來(lái)挑一個(gè)最常用的場(chǎng)景,來(lái)看看這個(gè)是什么一回事:
else if (process.argv[1] && process.argv[1] !== '-') {const path = NativeModule.require('path');process.argv[1] = path.resolve(process.argv[1]);const CJSModule = NativeModule.require('internal/modules/cjs/loader');...CJSModule.runMain(); } 復(fù)制代碼這個(gè)過(guò)程就是熟悉的node index.js這個(gè)過(guò)程,可以看到的對(duì)于開(kāi)發(fā)者自己的js來(lái)說(shuō),在node中對(duì)應(yīng)的class是Module,相信這個(gè)文件大家很多人都了解,與NativeModule相類(lèi)似,不同的是,需要進(jìn)行路徑的解析和模塊的查找等,來(lái)大致的看一下這個(gè)文件,先從上面調(diào)用的runMain來(lái)看:
if (experimentalModules) {// ... } else {Module._load(process.argv[1], null, true); } 復(fù)制代碼Module
node中開(kāi)啟--experimental-modules可以加載es模塊,也就是可以不用babel轉(zhuǎn)義就可以使用import/export啦,這個(gè)不是重點(diǎn),重點(diǎn)來(lái)看普通的commonnjs模塊,process.argv[1]一般就是要執(zhí)行的入口文件,下面看看Module._load:
Module._load = function(request, parent, isMain) {if (parent) {debug('Module._load REQUEST %s parent: %s', request, parent.id);}// 查找文件具體位置var filename = Module._resolveFilename(request, parent, isMain);// 存在緩存,則不需要再次執(zhí)行var cachedModule = Module._cache[filename];if (cachedModule) {updateChildren(parent, cachedModule, true);return cachedModule.exports;}// 加載node原生模塊,原生模塊不需要緩存,因?yàn)镹ativeModule中也存在緩存if (NativeModule.nonInternalExists(filename)) {debug('load native module %s', request);return NativeModule.require(filename);}// 加載并執(zhí)行一個(gè)模塊var module = new Module(filename, parent);if (isMain) {process.mainModule = module;module.id = '.';}Module._cache[filename] = module;// 調(diào)用load方法進(jìn)行加載tryModuleLoad(module, filename);return module.exports; }; 復(fù)制代碼這里看每一個(gè)Module有一個(gè)parent的屬性,假如a.js中引入了b.js,那么Module b的parent就是Module a,利用resolveFilename可以得到文件具體的位置,這個(gè)過(guò)程而后調(diào)用load函數(shù)來(lái)加載文件,可以看到的是區(qū)分了幾種類(lèi)型,分別是.js .json .node,對(duì)應(yīng)的.js是讀文件然后執(zhí)行,.json是直接讀文件后JSON.parse一下,.node是調(diào)用dlopen,Module.compile于NativeModule.compile相類(lèi)似都是想包裹一層成為函數(shù),然后調(diào)用了vm編譯得到這個(gè)函數(shù),最后傳入?yún)?shù)來(lái)執(zhí)行,對(duì)于Module來(lái)說(shuō),包裹的代碼如下:
Module.wrapper = ['(function (exports, require, module, __filename, __dirname) { ','\n});' ]; 復(fù)制代碼執(zhí)行完上述過(guò)程后,前期工作就已經(jīng)做得比較充分了,再次回到最后一個(gè)Start函數(shù)來(lái)看,從代碼中可以看到開(kāi)始了node的event loop,這就是node的初始化過(guò)程,關(guān)于event loop需要對(duì)libuv有一定的了解,可以說(shuō)node真正離不開(kāi)的是libuv,具體這方面的東西,可以繼續(xù)關(guān)注我后面的文章
總結(jié)
總結(jié)一下這個(gè)過(guò)程,以首次加載沒(méi)有任何緩存的情況開(kāi)看:require('fs'),先是調(diào)用了Module.require,而后發(fā)現(xiàn)為原生模塊,于是調(diào)用NativeModule.require,從NativeModule._source將lib/fs的內(nèi)容拿出來(lái)包裹一下然后執(zhí)行,這個(gè)文件第一行就可以看到process.binding,這個(gè)本質(zhì)上是加載原生的c++模塊,這個(gè)模塊在初始化的時(shí)候?qū)⑵渥?cè)到了一個(gè)鏈表中,加載的過(guò)程就是將其拿出來(lái)然后執(zhí)行
以上內(nèi)容如果有錯(cuò)誤的地方,還請(qǐng)大佬指出,萬(wàn)分感激,另外一件重要的事情就是:我所在團(tuán)隊(duì)也在招人,如果有興趣可以將簡(jiǎn)歷發(fā)至zhoupeng.1996@bytedance.com
轉(zhuǎn)載于:https://juejin.im/post/5b93fdd76fb9a05cf7157525
總結(jié)
以上是生活随笔為你收集整理的nodejs源码—初始化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 基于【CentOS-7+ Ambari
- 下一篇: 透彻理解Spring事务设计思想之手写实