<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node 中如何引入一個模塊及其細節(jié)

          共 5985字,需瀏覽 12分鐘

           ·

          2020-07-27 08:21

          node 環(huán)境中,有兩個內(nèi)置的全局變量無需引入即可直接使用,并且無處不見,它們構(gòu)成了 nodejs 的模塊體系: modulerequire。以下是一個簡單的示例

          const?fs?=?require('fs')

          const?add?=?(x,?y)?=>?x?+?y

          module.exports?=?add

          雖然它們在平常使用中僅僅是引入與導(dǎo)出模塊,但稍稍深入,便可見乾坤之大。在業(yè)界可用它們做一些比較 trick 的事情,雖然我不大建議使用這些黑科技,但稍微了解還是很有必要。

          1. 如何在不重啟應(yīng)用時熱加載模塊?如 require 一個 json 文件時會產(chǎn)生緩存,但是重寫文件時如何 watch
          2. 如何通過不侵入代碼進行打印日志
          3. 循環(huán)引用會產(chǎn)生什么問題?

          module wrapper

          當(dāng)我們使用 node 中寫一個模塊時,實際上該模塊被一個函數(shù)包裹,如下所示:

          (function(exports,?require,?module,?__filename,?__dirname)?{
          ??//?所有的模塊代碼都被包裹在這個函數(shù)中
          ??const?fs?=?require('fs')

          ??const?add?=?(x,?y)?=>?x?+?y

          ??module.exports?=?add
          });

          因此在一個模塊中自動會注入以下變量:

          • exports
          • require
          • module
          • __filename
          • __dirname

          module

          調(diào)試最好的辦法就是打印,我們想知道 module 是何方神圣,那就把它打印出來!

          const?fs?=?require('fs')

          const?add?=?(x,?y)?=>?x?+?y

          module.exports?=?add

          console.log(module)
          d9ae55fd92e7c90b73e7007865baa301.webp
          • module.id: 如果是 . 代表是入口模塊,否則是模塊所在的文件名,可見如下的 koa
          • module.exports: 模塊的導(dǎo)出
          2d4d44436b48442a647f081b0488bb40.webpkoa module

          module.exports 與 exports

          ?

          `module.exports` 與 `exports` 有什么關(guān)系?[1]

          ?

          從以下源碼中可以看到 module wrapper 的調(diào)用方 module._compile 是如何注入內(nèi)置變量的,因此根據(jù)源碼很容易理解一個模塊中的變量:

          • exports: 實際上是 module.exports 的引用
          • require: 大多情況下是 Module.prototype.require
          • module
          • __filename
          • __dirname: path.dirname(__filename)
          //?/internal/modules/cjs/loader.js:1138

          Module.prototype._compile?=?function(content,?filename)?{
          ??//?...
          ??const?dirname?=?path.dirname(filename);
          ??const?require?=?makeRequireFunction(this,?redirects);
          ??let?result;

          ??//?從中可以看出:exports = module.exports
          ??const?exports?=?this.exports;
          ??const?thisValue?=?exports;
          ??const?module?=?this;
          ??if?(requireDepth?===?0)?statCache?=?new?Map();
          ??if?(inspectorWrapper)?{
          ????result?=?inspectorWrapper(compiledWrapper,?thisValue,?exports,
          ??????????????????????????????require,?module,?filename,?dirname);
          ??}?else?{
          ????result?=?compiledWrapper.call(thisValue,?exports,?require,?module,
          ??????????????????????????????????filename,?dirname);
          ??}
          ??//?...
          }

          require

          通過 node 的 REPL 控制臺,或者在 VSCode 中輸出 require 進行調(diào)試,可以發(fā)現(xiàn) require 是一個極其復(fù)雜的對象

          7eac04c4934698b79a9362dd73e91e4d.webprequire

          從以上 module wrapper 的源碼中也可以看出 requiremakeRequireFunction 函數(shù)生成,如下

          //?/internal/modules/cjs/helpers.js:33

          function?makeRequireFunction(mod,?redirects)?{
          ??const?Module?=?mod.constructor;

          ??let?require;
          ??if?(redirects)?{
          ????//?...
          ??}?else?{
          ????//?require?實際上是?Module.prototype.require
          ????require?=?function?require(path)?{
          ??????return?mod.require(path);
          ????};
          ??}

          ??function?resolve(request,?options)?{?//?...?}

          ??require.resolve?=?resolve;

          ??function?paths(request)?{
          ????validateString(request,?'request');
          ????return?Module._resolveLookupPaths(request,?mod);
          ??}

          ??resolve.paths?=?paths;

          ??require.main?=?process.mainModule;

          ??//?Enable?support?to?add?extra?extension?types.
          ??require.extensions?=?Module._extensions;

          ??require.cache?=?Module._cache;

          ??return?require;
          }
          ?

          關(guān)于 require 更詳細的信息可以去參考官方文檔: Node API: require[2]

          ?

          require(id)

          require 函數(shù)被用作引入一個模塊,也是平常最常見最常用到的函數(shù)

          //?/internal/modules/cjs/loader.js:1019

          Module.prototype.require?=?function(id)?{
          ??validateString(id,?'id');
          ??if?(id?===?'')?{
          ????throw?new?ERR_INVALID_ARG_VALUE('id',?id,
          ????????????????????????????????????'must?be?a?non-empty?string');
          ??}
          ??requireDepth++;
          ??try?{
          ????return?Module._load(id,?this,?/*?isMain?*/?false);
          ??}?finally?{
          ????requireDepth--;
          ??}
          }

          require 引入一個模塊時,實際上通過 Module._load 載入,大致的總結(jié)如下:

          1. 如果 Module._cache 命中模塊緩存,則直接取出 module.exports,加載結(jié)束
          2. 如果是 NativeModule,則 loadNativeModule 加載模塊,如 fs、http、path 等模塊,加載結(jié)束
          3. 否則,使用 Module.load 加載模塊,當(dāng)然這個步驟也很長,下一章節(jié)再細講
          //?/internal/modules/cjs/loader.js:879

          Module._load?=?function(request,?parent,?isMain)?{
          ??let?relResolveCacheIdentifier;
          ??if?(parent)?{
          ????//?...
          ??}

          ??const?filename?=?Module._resolveFilename(request,?parent,?isMain);

          ??const?cachedModule?=?Module._cache[filename];

          ??//?如果命中緩存,直接取緩存
          ??if?(cachedModule?!==?undefined)?{
          ????updateChildren(parent,?cachedModule,?true);
          ????return?cachedModule.exports;
          ??}

          ??//?如果是?NativeModule,加載它
          ??const?mod?=?loadNativeModule(filename,?request);
          ??if?(mod?&&?mod.canBeRequiredByUsers)?return?mod.exports;

          ??//?Don't?call?updateChildren(),?Module?constructor?already?does.
          ??const?module?=?new?Module(filename,?parent);

          ??if?(isMain)?{
          ????process.mainModule?=?module;
          ????module.id?=?'.';
          ??}

          ??Module._cache[filename]?=?module;
          ??if?(parent?!==?undefined)?{?//?...?}

          ??let?threw?=?true;
          ??try?{
          ????if?(enableSourceMaps)?{
          ??????try?{
          ????????//?如果不是?NativeModule,加載它
          ????????module.load(filename);
          ??????}?catch?(err)?{
          ????????rekeySourceMap(Module._cache[filename],?err);
          ????????throw?err;?/*?node-do-not-add-exception-line?*/
          ??????}
          ????}?else?{
          ??????module.load(filename);
          ????}
          ????threw?=?false;
          ??}?finally?{
          ????//?...
          ??}

          ??return?module.exports;
          };

          require.cache

          「當(dāng)代碼執(zhí)行 require(lib) 時,會執(zhí)行 lib 模塊中的內(nèi)容,并作為一份緩存,下次引用時不再執(zhí)行模塊中內(nèi)容」

          這里的緩存指的就是 require.cache,也就是上一段指的 Module._cache

          //?/internal/modules/cjs/loader.js:899

          require.cache?=?Module._cache;

          這里有個小測試:

          ?

          有兩個文件: index.jsutils.jsutils.js 中有一個打印操作,當(dāng) index.js 引用 utils.js 多次時,utils.js 中的打印操作會執(zhí)行幾次。代碼示例如下

          ?

          「index.js」

          //?index.js

          //?此處引用兩次
          require('./utils')
          require('./utils')

          「utils.js」

          //?utils.js
          console.log('被執(zhí)行了一次')

          「答案是只執(zhí)行了一次」,因此 require.cache,在 index.js 末尾打印 require,此時會發(fā)現(xiàn)一個模塊緩存

          //?index.js

          require('./utils')
          require('./utils')

          console.log(require)
          da7d8f20f399cdd3881fc4506e919613.webp

          那回到本章剛開始的問題:

          ?

          如何不重啟應(yīng)用熱加載模塊呢?

          ?

          答:「刪掉 Module._cache,但同時會引發(fā)問題,如這種 一行 delete require.cache 引發(fā)的內(nèi)存泄漏血案[3]

          所以說嘛,這種黑魔法大幅修改核心代碼的東西開發(fā)環(huán)境玩一玩就可以了,千萬不要跑到生產(chǎn)環(huán)境中去,畢竟黑魔法是不可控的。

          總結(jié)

          1. 模塊中執(zhí)行時會被 module wrapper 包裹,并注入全局變量 requiremodule
          2. module.exportsexports 的關(guān)系實際上是 exports = module.exports
          3. require 實際上是 module.require
          4. require.cache 會保證模塊不會被執(zhí)行多次
          5. 不要使用 delete require.cache 這種黑魔法?

          ??看完三件事

          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點贊,讓更多的人也能看到介紹內(nèi)容(收藏不點贊,都是耍流氓-_-)
          2. 關(guān)注公眾號“前端勸退師”,不定期分享原創(chuàng)知識。
          3. 也看看其他文章

          勸退師個人微信:huab119

          也可以來我的GitHub博客里拿所有文章的源文件:

          前端勸退指南:https://github.com/roger-hiro/BlogFN一起玩耍呀。



          Reference

          [1]

          module.exportsexports 有什么關(guān)系?: https://github.com/shfshanyue/Daily-Question/issues/351

          [2]

          Node API: require: https://nodejs.org/api/modules.html#modules_require_id

          [3]

          一行 delete require.cache 引發(fā)的內(nèi)存泄漏血案: https://zhuanlan.zhihu.com/p/34702356

          [4]

          shfshanyue/blog: https://github.com/shfshanyue/blog

          [5]

          前端工程化系列: https://github.com/shfshanyue/blog#%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96

          [6]

          Node進階系列: https://github.com/shfshanyue/blog#node-%E5%AE%9E%E8%B7%B5


          瀏覽 13
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本一区二区三区视频免费看 | AV一区波多野结衣 | 国产欧美操逼 | 国产啊啊| 五月天激情导航 |