成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

nodejs源碼—初始化

douzifly / 1743人閱讀

摘要:概述相信很多的人,每天在終端不止一遍的執(zhí)行著這條命令,對于很多人來說,它就像一個黑盒,并不知道背后到底發(fā)生了什么,本文將會為大家揭開這個神秘的面紗,由于本人水平有限,所以只是講一個大概其,主要關(guān)注的過程就是模塊的初始化,和的部分基本沒有深入

概述

相信很多的人,每天在終端不止一遍的執(zhí)行著node這條命令,對于很多人來說,它就像一個黑盒,并不知道背后到底發(fā)生了什么,本文將會為大家揭開這個神秘的面紗,由于本人水平有限,所以只是講一個大概其,主要關(guān)注的過程就是node模塊的初始化,event loopv8的部分基本沒有深入,這些部分可以關(guān)注一下我以后的文章。(提示本文非常的長,希望大家不要看煩~)

node是什么?

這個問題很多人都會回答就是v8 + libuv,但是除了這個兩個庫以外node還依賴許多優(yōu)秀的開源庫,可以通過process.versions來看一下:

http_parser主要用于解析http數(shù)據(jù)包的模塊,在這個庫的作者也是ry,一個純c的庫,無任何依賴

v8這個大家就非常熟悉了,一個優(yōu)秀的js引擎

uv這個就是ry實(shí)現(xiàn)的libuv,其封裝了libevIOCP,實(shí)現(xiàn)了跨平臺,node中的i/o就是它,盡管js是單線程的,但是libuv并不是,其有一個線程池來處理這些i/o操作。

zlib主要來處理壓縮操作,諸如熟悉的gzip操作

aresc-ares,這個庫主要用于解析dns,其也是異步的

modules就是node的模塊系統(tǒng),其遵循的規(guī)范為commonjs,不過node也支持了ES模塊,不過需要加上參數(shù)并且文件名后綴需要為mjs,通過源碼看,nodeES模塊的名稱作為了一種url來看待,具體可以參見這里

nghttp2如其名字一樣,是一個http2的庫

napi是在node8出現(xiàn),node10穩(wěn)定下來的,可以給編寫node原生模塊更好的體驗(yàn)(終于不用在依賴于nan,每次更換node版本還要重新編譯一次了)

openssl非常著名的庫,tls模塊依賴于這個庫,當(dāng)然還包括https

icu就是small-icu,主要用于解決跨平臺的編碼問題,versions對象中的unicode,cldr,tz也源自icu,這個的定義可以參見這里

從這里可以看出的是process對象在node中非常的重要,個人的理解,其實(shí)node與瀏覽器端最主要的區(qū)別,就在于這個process對象

注:node只是用v8來進(jìn)行js的解析,所以不一定非要依賴v8,也可以用其他的引擎來代替,比如利用微軟的ChakraCore,對應(yīng)的node倉庫

node初始化

經(jīng)過上面的一通分析,對node的所有依賴有了一定的了解,下面來進(jìn)入正題,看一下node的初始化過程:

挖坑

node_main.cc為入口文件,可以看到的是除了調(diào)用了node::Start之外,還做了兩件事情:

NODE_SHARED_MODE忽略SIGPIPE信號

SIGPIPE信號出現(xiàn)的情況一般在socket收到RST packet之后,扔向這個socket寫數(shù)據(jù)時產(chǎn)生,簡單來說就是clientserver發(fā)請求,但是這時候client已經(jīng)掛掉,這時候就會產(chǎn)生SIGPIPE信號,產(chǎn)生這個信號會使server端掛掉,其實(shí)node::PlatformInit中也做了這種操作,不過只是針對non-shared lib build

改變緩沖行為

stdout的默認(rèn)緩沖行為為_IOLBF(行緩沖),但是對于這種來說交互性會非常的差,所以將其改為_IONBF(不緩沖)

探索

node.cc文件中總共有三個Start函數(shù),先從node_main.cc中掉的這個Start函數(shù)開始看:

int Start(int argc, char** argv) {
  // 退出之前終止libuv的終端行為,為正常退出的情況
  atexit([] () { uv_tty_reset_mode(); });
  // 針對平臺進(jìn)行初始化
  PlatformInit();
  // ...
  Init(&argc, const_cast(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);
}

上面函數(shù)只保留了一些關(guān)鍵不走,先來看看PlatformInit

PlatfromInit

unix中將一切都看作文件,進(jìn)程啟動時會默認(rèn)打開三個i/o設(shè)備文件,也就是stdin stdout stderr,默認(rèn)會分配0 1 2三個描述符出去,對應(yīng)的文件描述符常量為STDIN_FILENO STDOUT_FILENO STDERR_FILENO,而windows中沒有文件描述符的這個概念,對應(yīng)的是句柄,PlatformInit首先是檢查是否將這個三個文件描述符已經(jīng)分配出去,若沒有,則利用open("/dev/null", O_RDWR)分配出去,對于windows做了同樣的操作,分配句柄出去,而且windows只做了這一個操作;對于unix來說還會針對SIGINT(用戶調(diào)用Ctrl-C時發(fā)出)和SIGTERMSIGTERMSIGKILL類似,但是不同的是該信號可以被阻塞和處理,要求程序自己退出)信號來做一些特殊處理,這個處理與正常退出時一樣;另一個重要的事情就是下面這段代碼:

  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;
      // 對于mac來說 hard limit 為unlimited
      // 但是內(nèi)核有限制最大的文件描述符,超過這個限制則設(shè)置失敗
      if (setrlimit(RLIMIT_NOFILE, &lim)) {
        max = lim.rlim_cur;
      } else {
        min = lim.rlim_cur;
      }
    } while (min + 1 < max);
  }

這個件事情也就是提高一個進(jìn)程允許打開的最大文件描述符,但是在mac上非常的奇怪,執(zhí)行ulimit -H -n得到hard limitunlimited,所以我認(rèn)為mac上的最大文件描述符會被設(shè)置為1 << 20,但是最后經(jīng)過實(shí)驗(yàn)發(fā)現(xiàn)最大只能為24576,非常的詭異,最后經(jīng)過一頓搜索,查到了原來mac的內(nèi)核對能打開的文件描述符也有限制,可以用sysctl -A | grep kern.maxfiles進(jìn)行查看,果然這個數(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)

從名字也可以看出上面的過程是進(jìn)行c++模塊的初始化,node利用了一些宏定義的方式,主要關(guān)注NODE_BUILTIN_STANDARD_MODULES這個宏:

#define NODE_BUILTIN_STANDARD_MODULES(V)                                      
    V(async_wrap)                                                             
    V(buffer)
    ...

結(jié)合上面的定義,可以得出編譯后的代碼大概為:

void RegisterBuiltinModules() {
  _register_async_wrap();
  _register_buffer();
}

而這些_register又是從哪里來的呢?以buffer來說,對應(yīng)c++文件為src/node_buffer.cc,來看這個文件的最后一行,第二個參數(shù)是模塊的初始化函數(shù):

NODE_BUILTIN_MODULE_CONTEXT_AWARE(buffer, node::Buffer::Initialize)

這個宏存在于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ā)現(xiàn)調(diào)用的_register_buffer實(shí)質(zhì)上調(diào)用的是node_module_register(&_module),每一個c++模塊對應(yīng)的為一個node_module結(jié)構(gòu)體,再來看看node_module_register發(fā)生了什么:

extern "C" void node_module_register(void* m) {
  struct node_module* mp = reinterpret_cast(m);

  if (mp->nm_flags & NM_F_BUILTIN) {
    mp->nm_link = modlist_builtin;
    modlist_builtin = mp;
  }
  ...
}

由此可以見,c++模塊被存儲在了一個鏈表中,后面process.binding()本質(zhì)上就是在這個鏈表中查找對應(yīng)c++模塊,node_module是鏈表中的一個節(jié)點(diǎn),除此之外Init還初始化了一些變量,這些變量基本上都是取決于環(huán)境變量用getenv獲得即可

v8初始化

到執(zhí)行完Init為止,還沒有涉及的jsc++的交互,在將一些環(huán)境初始化之后,就要開始用v8這個大殺器了,v8_platform是一個結(jié)構(gòu)體,可以理解為是node對于v8v8::platform一個封裝,緊接著的就是對v8進(jìn)行初始化,自此開始具備了與js進(jìn)行交互的能力,初始化v8之后,創(chuàng)建了一個libuv事件循環(huán)就進(jìn)入了下一個Start函數(shù)

第二個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
      allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator);
  Isolate* const isolate = NewIsolate(allocator.get());
  // ...
  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
  }
}

首先創(chuàng)建了一個v8Isolate(隔離),隔離在v8中非常常見,仿佛和進(jìn)程一樣,不同隔離不共享資源,有著自己得堆棧,但是正是因?yàn)檫@個原因在多線程的情況下,要是對每一個線程都創(chuàng)建一個隔離的話,那么開銷會非常的大(可喜可賀的是node有了worker_threads),這時候可以借助Locker來進(jìn)行同步,同時也保證了一個Isolate同一時刻只能被一個線程使用;下面兩行就是v8的常規(guī)套路,下一步一般就是創(chuàng)建一個Context(最簡化的一個流程可以參見v8的hello world),HandleScope叫做句柄作用域,一般都是放在函數(shù)的開頭,來管理函數(shù)創(chuàng)建的一些句柄(水平有限,暫時不深究,先挖個坑);第二個Start的主要流程就是這個,下面就會進(jìn)入最后一個Start函數(shù),這個函數(shù)可以說是非常的關(guān)鍵,會揭開所有的謎題

解開謎題
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 = 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);
  // ...

可以見到v8的常見套路,創(chuàng)建了一個上下文,這個上下文就是js的執(zhí)行環(huán)境,Context::Scope是用來管理這個Context,Environment可以理解為一個node的運(yùn)行環(huán)境,記錄了isolate,event loop等,Start的過程主要是做了一些libuv的初始化以及process對象的定義:

  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);

SetupProcessObject生成了一個c++層面上的process對象,這個已經(jīng)基本上和平時node中的process對象一致,但是還會有一些出入,比如沒有binding等,完成了這個過程之后就開始了LoadEnvironment

LoadEnvironment
Local loaders_name =
    FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
MaybeLocal loaders_bootstrapper =
    GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
Local node_name =
    FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
MaybeLocal node_bootstrapper =
    GetBootstrapper(env, NodeBootstrapperSource(env), node_name);

先將lib/internal/bootstrap文件夾下的兩個文件讀進(jìn)來,然后利用GetBootstrapper來執(zhí)行js代碼分別得到了一個函數(shù),一步步來看,先看看GetBootstrapper為什么可以執(zhí)行js代碼,查看這個函數(shù)可以發(fā)現(xiàn)主要是因?yàn)?b>ExecuteString:

MaybeLocal script =
    v8::Script::Compile(env->context(), source, &origin);
...
MaybeLocal result = script.ToLocalChecked()->Run(env->context());

這個主要利用了v8的能力,對js文件進(jìn)行了解析和執(zhí)行,打開loaders.js看看其參數(shù),需要五個,撿兩個最重要的來說,分別是processgetBinding,這里面往后繼續(xù)看LoadEnvironment發(fā)現(xiàn)process對象就是剛剛生成的,而getBinding是函數(shù)GetBinding

node_module* mod = get_builtin_module(*module_v);
Local 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 _source
  exports = Object::New(env->isolate());
  DefineJavaScript(env, exports);
} else {
  return ThrowIfNoSuchModule(env, *module_v);
}

args.GetReturnValue().Set(exports);

其作用就是根據(jù)傳參來初始化指定的模塊,當(dāng)然也有比較特殊的兩個分別是constantsnatives(后面再看),get_builtin_module調(diào)用的就是FindModule,還記得之前在Init過程中將模塊都注冊到的鏈表嗎?FindModule就是遍歷這個鏈表找到相應(yīng)的模塊:

struct node_module* mp;
for (mp = list; mp != nullptr; mp = mp->nm_link) {
  if (strcmp(mp->nm_modname, name) == 0)
    break;
}

InitModule就是調(diào)用之前注冊模塊定義的初始化函數(shù),還以buffer看的話,就是執(zhí)行node::Buffer::Initialize函數(shù),打開著函數(shù)來看和平時寫addon的方式一樣,也會暴露一個對象出來供js調(diào)用;LoadEnvironment下面就是將process, GetBinding等作為傳入傳給上面生成好的函數(shù)并且利用v8來執(zhí)行,來到了大家熟悉的領(lǐng)域,來看看loaders.js

const moduleLoadList = [];
ObjectDefineProperty(process, "moduleLoadList", {
  value: moduleLoadList,
  configurable: true,
  enumerable: true,
  writable: false
});

定義了一個已經(jīng)加載的Module的數(shù)組,也可以在node通過process.moduleLoadList來看看加載了多少的原生模塊進(jìn)來

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;
};

終于到了這個方法,翻看lib中的js文件,有著非常多的這種調(diào)用,這個函數(shù)就是對GetBinding做了一個js層面的封裝,做的無非是查看一下這個模塊是否已經(jīng)加載完成了,是的話直接返回回去,不需要再次初始化了,所以利用prcoess.binding加載了對應(yīng)的c++模塊(可以執(zhí)行一下process.binding("buffer"),然后再去node_buffer.cc中看看)繼續(xù)向下看,會發(fā)現(xiàn)定義了一個class就是NativeModule,發(fā)現(xiàn)其有一個靜態(tài)屬性:

加載js
NativeModule._source = getBinding("natives");

返回到GetBinding函數(shù),看到的是一個if分支就是這種情況:

exports = Object::New(env->isolate());
DefineJavaScript(env, exports);

來看看DefineJavaScript發(fā)生了什么樣的事情,這個函數(shù)發(fā)現(xiàn)只能在頭文件(node_javascript.h)里面找到,但是根本找不到具體的實(shí)現(xiàn),這是個什么鬼???去翻一下node.gyp文件發(fā)現(xiàn)這個文件是用js2c.py這個文件生成的,去看一下這個python文件,可以發(fā)現(xiàn)許多的代碼模板,每一個模板都是用Render返回的,data參數(shù)就是js文件的內(nèi)容,最終會被轉(zhuǎn)換為c++中的byte數(shù)組,同時定義了一個將其轉(zhuǎn)換為字符串的方法,那么問題來了,這些文件都是那些呢?答案還是在node.gyp中,就是library_files數(shù)組,發(fā)現(xiàn)包含了lib下的所有的文件和一些dep下的js文件,DefineJavaScript這個文件做的就是將待執(zhí)行的js代碼注冊下,所以NativeModule._source中存儲的是一些待執(zhí)行的js代碼,來看一下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ā)現(xiàn)NativeModule也有著緩存的策略,require先把其放到_cache中再次require就不會像第一次那樣執(zhí)行這個模塊,而是直接用緩存中執(zhí)行好的,后面說的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) {",
  "
});"
];

首先從_source中取出相應(yīng)的模塊,然后對這個模塊進(jìn)行包裹成一個函數(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);

本質(zhì)上就是調(diào)用了vm編譯自婦產(chǎn)得到函數(shù),然后給其傳入了一些參數(shù)并執(zhí)行,this.exports就是一個對象,require區(qū)分了一下是否加載node依賴的js文件,this也就是參數(shù)module,這也說明了兩者的關(guān)系,exports就是module的一個屬性,也解釋了為什么exports.xx之后再指定module.exports = yy會將xx忽略掉,還記得LoadEnvironment嗎?bootstrap/loaders.js執(zhí)行完之后執(zhí)行了bootstrap/node.js,可以說這個文件是node真正的入口,比如定義了global對象上的屬性,比如console setTimeout等,由于篇幅有限,來挑一個最常用的場景,來看看這個是什么一回事:

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();
}

這個過程就是熟悉的node index.js這個過程,可以看到的對于開發(fā)者自己的js來說,在node中對應(yīng)的classModule,相信這個文件大家很多人都了解,與NativeModule相類似,不同的是,需要進(jìn)行路徑的解析和模塊的查找等,來大致的看一下這個文件,先從上面調(diào)用的runMain來看:

if (experimentalModules) {
  // ...
} else {
  Module._load(process.argv[1], null, true);
}
Module

node中開啟--experimental-modules可以加載es模塊,也就是可以不用babel轉(zhuǎn)義就可以使用import/export啦,這個不是重點(diǎn),重點(diǎn)來看普通的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í)行一個模塊
  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;
};

這里看每一個Module有一個parent的屬性,假如a.js中引入了b.js,那么Module bparent就是Module a,利用resolveFilename可以得到文件具體的位置,這個過程而后調(diào)用load函數(shù)來加載文件,可以看到的是區(qū)分了幾種類型,分別是.js .json .node,對應(yīng)的.js是讀文件然后執(zhí)行,.json是直接讀文件后JSON.parse一下,.node是調(diào)用dlopenModule.compileNativeModule.compile相類似都是想包裹一層成為函數(shù),然后調(diào)用了vm編譯得到這個函數(shù),最后傳入?yún)?shù)來執(zhí)行,對于Module來說,包裹的代碼如下:

Module.wrapper = [
  "(function (exports, require, module, __filename, __dirname) { ",
  "
});"
];

執(zhí)行完上述過程后,前期工作就已經(jīng)做得比較充分了,再次回到最后一個Start函數(shù)來看,從代碼中可以看到開始了nodeevent loop,這就是node的初始化過程,關(guān)于event loop需要對libuv有一定的了解,可以說node真正離不開的是libuv,具體這方面的東西,可以繼續(xù)關(guān)注我后面的文章

總結(jié)

總結(jié)一下這個過程,以首次加載沒有任何緩存的情況開看:require("fs"),先是調(diào)用了Module.require,而后發(fā)現(xiàn)為原生模塊,于是調(diào)用NativeModule.require,從NativeModule._sourcelib/fs的內(nèi)容拿出來包裹一下然后執(zhí)行,這個文件第一行就可以看到process.binding,這個本質(zhì)上是加載原生的c++模塊,這個模塊在初始化的時候?qū)⑵渥缘搅艘粋€鏈表中,加載的過程就是將其拿出來然后執(zhí)行

以上內(nèi)容如果有錯誤的地方,還請大佬指出,萬分感激,另外一件重要的事情就是:我所在團(tuán)隊(duì)也在招人,如果有興趣可以將簡歷發(fā)至[email protected]

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97569.html

相關(guān)文章

  • 解析nodeJS模塊源碼 親手打造基于ES6的觀察者系統(tǒng)

    摘要:為指定事件注冊一個單次監(jiān)聽器,即監(jiān)聽器最多只會觸發(fā)一次,觸發(fā)后立刻解除該監(jiān)聽器。移除指定事件的某個監(jiān)聽器,監(jiān)聽器必須是該事件已經(jīng)注冊過的監(jiān)聽器。返回指定事件的監(jiān)聽器數(shù)組。如何創(chuàng)建空對象我們已經(jīng)了解到,是要來儲存監(jiān)聽事件監(jiān)聽器數(shù)組的。 毫無疑問,nodeJS改變了整個前端開發(fā)生態(tài)。本文通過分析nodeJS當(dāng)中events模塊源碼,由淺入深,動手實(shí)現(xiàn)了屬于自己的ES6事件觀察者系統(tǒng)。千萬不...

    csRyan 評論0 收藏0
  • 造輪子之 npm i -g creatshare-app-init 源碼淺析

    摘要:剛剛在里說明的回調(diào)函數(shù)綁定在命令下。使用開源協(xié)議源代碼都放在目錄下目錄要對不同的代碼進(jìn)行合理的分層。,我是韓亦樂,現(xiàn)任本科軟工男一枚。 以我的小經(jīng)驗(yàn)來看,軟件萌新寫出來的代碼大多無法直視。具體現(xiàn)象包括空格和換行符亂用、文件夾和變量的命名多使用拼音等。坐不住的我,便想到了通過 ESLint 配置文件來規(guī)范實(shí)驗(yàn)室的 JavaScript 代碼規(guī)范的 Idea。 于是巧遇前實(shí)驗(yàn)室畢業(yè)學(xué)長曾經(jīng)...

    adie 評論0 收藏0
  • 通過源碼解析 Node.js 啟動時第一個執(zhí)行的 js 文件:bootstrap_node.js

    摘要:注很多以前的源碼分析文章中,所寫的第一個執(zhí)行的文件代碼為,但這個文件在中已被移除,并被拆解為了等其他下的文件,為正文作為第一段被執(zhí)行的代碼,它的歷史使命免不了就是進(jìn)行一些環(huán)境和全局變量的初始化工作。 大家可能會好奇,在 Node.js 啟動后,第一個執(zhí)行的 JavaScript 文件會是哪個?它具體又會干些什么事? 一步步來看,翻開 Node.js 的源碼,不難看出,入口文件在 src...

    TNFE 評論0 收藏0
  • jest jenkins搭建自動化CI測試教程

    摘要:想做自動化接口測試時,我也按照打包鏡像的套路走,感覺走到死胡同。終于自己走通了一遍。點(diǎn)擊進(jìn)去可以發(fā)現(xiàn)測試報(bào)告。注意測試報(bào)告并不是生成的。我的項(xiàng)目目錄如下最后如果你也需要自動化接口測試工具可以試試我最近寫的一個工具。 關(guān)于jest和jenkins集成,我走了一些彎路。之前一直用jenkins打包nodejs鏡像。想做nodejs自動化接口測試時,我也按照打包鏡像的套路走,感覺走到死胡同。...

    ztyzz 評論0 收藏0
  • JavaScript 開發(fā)者所需要知道的 V8(一):V8 In NodeJS

    摘要:歡迎來我的博客閱讀開發(fā)者所需要知道的一是一款擁有自動垃圾回收功能的編程語言。它隨著的第一版發(fā)布而發(fā)布以及開源。年月,基金宣布和合并,合并版本在未來發(fā)布。年月日,官方公布又一個新的名為的優(yōu)化編譯器,主要提供的新語法,以及提高性能。 歡迎來我的博客閱讀:「JavaScript 開發(fā)者所需要知道的 V8(一):V8 In NodeJS」 Motivation JavaScript 是一款擁有...

    Lemon_95 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<