<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.js 中的 require 是如何工作的?

          共 8050字,需瀏覽 17分鐘

           ·

          2020-09-26 06:42

          作者:FESKY 鏈接:https://juejin.im/post/6844903957752463374

          作為前端開發(fā)者,不可避免每天都要跟?Node.js?打交道。Node?遵循?Commonjs?規(guī)范,規(guī)范的核心是通過?require?來(lái)加載依賴的其他模塊。我們已經(jīng)常習(xí)慣于使用社區(qū)提供的各種庫(kù),但對(duì)于模塊引用的背后原理知之甚少。這篇文章通過源碼閱讀,淺析在?commonjs?規(guī)范中?require?背后的工作原理。

          require 從哪里來(lái)?

          大家都知道,在?node js?的模塊/文件中,有些“全局”變量是可以直接使用的,比如?require, module, __dirname, __filename, exports。其實(shí)這些變量或方法并不是“全局”的,而是在?commonjs?模塊加載中, 通過包裹的形式,提供的局部變量。

          module.exports?=?function?()?{
          ????console.log(__dirname);
          }

          經(jīng)過?compile?之后,就有了?module__dirname?等變量可以直接使用。

          (function?(exports,?require,?module,?__filename,?__dirname)?{
          ????module.exports?=?function?()?{
          ????????console.log(__dirname);
          ????}
          })

          這也可以很好解答初學(xué)者常常會(huì)困惑的問題,為什么給?exports?賦值,require?之后得到的結(jié)果是?undefined?

          //?直接給?exports?賦值是不會(huì)生效的
          (function?(exports,?module)?{
          ????exports?=?function?()?{
          ????}
          })(m.exports,?m)

          return?m.exports;

          直接賦值只是修改了局部變臉?exports?的值。最終?export?出去的?module.exports沒有被賦值。

          require 的查找過程

          文檔中描述得非常清楚,簡(jiǎn)化版?require?模塊的查找過程如下:在?Y?路徑下,require(X)

          1. 如果X是內(nèi)置模塊(http, fs, path 等), 直接返回內(nèi)置模塊,不再執(zhí)行
          2. 如果 X 以 '/' 開頭,把 Y 設(shè)置為文件系統(tǒng)根目錄
          3. 如果 X 以 './', '/', '../' 開頭 a. 按照文件的形式加載(Y + X),根據(jù) extensions 依次嘗試加載文件 [X, X.js, X.json, X.node] 如果存在就返回該文件,不再繼續(xù)執(zhí)行。b. 按照文件夾的形式加載(Y + X),如果存在就返回該文件,不再繼續(xù)執(zhí)行,若找不到將拋出錯(cuò)誤 a. 嘗試解析路徑下 package.json main 字段 b. 嘗試加載路徑下的 index 文件(index.js, index.json, index.node)
          4. 搜索 NODE_MODULE,若存在就返回模塊 a. 從路徑 Y 開始,一層層往上找,嘗試加載(路徑 + 'node_modules/' + X) b. 在 GLOBAL_FOLDERS node_modules 目錄中查找 X
          5. 拋出 "Not Found" Error 復(fù)制代碼例如在?/Users/helkyle/projects/learning-module/foo.js` 中 require('bar') 將會(huì)從`/Users/helkyle/projects/learning-module/?開始逐層往上查找bar?模塊(不是以?'./', '/', '../'?開頭)。
          '/Users/helkyle/projects/learning-module/node_modules',
          '/Users/helkyle/projects/node_modules',
          '/Users/helkyle/node_modules',
          '/Users/node_modules',
          '/node_modules'

          需要注意的是,在使用?npm link?功能的時(shí)候,被?link?模塊內(nèi)的?require?會(huì)以被?link?模塊在文件系統(tǒng)中的絕對(duì)路徑進(jìn)行查找,而不是?main module?所在的路徑。舉個(gè)例子,假設(shè)有兩個(gè)模塊。

          /usr/lib/foo
          /usr/lib/bar

          通過?link?形式在?foo?模塊中?link bar,會(huì)產(chǎn)生軟連?/usr/lib/foo/node_modules/bar?指向?/usr/lib/bar,這種情況下?bar?模塊下?require('quux')?的查找路徑是?/usr/lib/bar/node_modules/而不是?/usr/lib/foo/node_modules我之前踩過的坑

          Cache 機(jī)制

          在實(shí)踐過程中能了解到,實(shí)際上?Node module require?的過程會(huì)有緩存。也就是兩次?require?同一個(gè)?module會(huì)得到一樣的結(jié)果。

          //?a.js
          module.exports?=?{
          ????foo:?1,
          };

          //?b.js
          const?a1?=?require('./a.js');
          a1.foo?=?2;

          const?a2?=?require('./a.js');

          console.log(a2.foo);?//?2
          console.log(a1?===?a2);?//?true

          執(zhí)行?node b.js,可以看到,第二次?require a.js?跟第一次?require?得到的是相同的模塊引用。從源碼上看,require?是對(duì)?module?常用方法的封裝。

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

          ??let?require;
          ??//?簡(jiǎn)化其他代碼
          ??require?=?function?require(path)?{
          ????return?mod.require(path);
          ??};

          ??function?resolve(request,?options)?{
          ????validateString(request,?'request');
          ????return?Module._resolveFilename(request,?mod,?false,?options);
          ??}

          ??require.resolve?=?resolve;

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

          ??resolve.paths?=?paths;
          ??require.main?=?process.mainModule;
          ??require.extensions?=?Module._extensions;
          ??require.cache?=?Module._cache;

          ??return?require;
          }

          跟蹤代碼看到,require()?最終調(diào)用的是?Module._load?方法:// 忽略代碼,看看?load?的過程發(fā)生了什么?

          Module._load?=?function(request,?parent,?isMain)?{
          ??//?調(diào)用?_resolveFilename?獲得模塊絕對(duì)路徑
          ??const?filename?=?Module._resolveFilename(request,?parent,?isMain);

          ??const?cachedModule?=?Module._cache[filename];
          ??if?(cachedModule?!==?undefined)?{
          ????//?如果存在緩存,直接返回緩存的?exports?對(duì)象
          ????return?cachedModule.exports;
          ??}
          ??//?內(nèi)建模塊直接返回
          ??const?mod?=?loadNativeModule(filename,?request,?experimentalModules);
          ??if?(mod?&&?mod.canBeRequiredByUsers)?return?mod.exports;

          ??//?創(chuàng)建新的?module?對(duì)象
          ??const?module?=?new?Module(filename,?parent);

          ??//?main?module?特殊處理
          ??if?(isMain)?{
          ????process.mainModule?=?module;
          ????module.id?=?'.';
          ??}
          ??//?緩存?module
          ??Module._cache[filename]?=?module;
          ??
          ??//?返回?module?exports?對(duì)象
          ??return?module.exports;
          };

          到這里,module cache?的原理也很清晰,模塊在首次加載后,會(huì)以模塊絕對(duì)路徑為?key?緩存在?Module._cache屬性上,再次?require?時(shí)會(huì)直接返回已緩存的結(jié)果以提高 效率。在控制臺(tái)打印?require.cache?看看。

          //?b.js
          require('./a.js');
          require('./a.js');

          console.log(require.cache);

          緩存中有兩個(gè)key,分別是?a.js, b.js?文件在系統(tǒng)中的絕對(duì)路徑。value?則是對(duì)應(yīng)模塊?load?之后的?module?對(duì)象。所以第二次?require('./a.js')?的結(jié)果是?require.cache['/Users/helkyle/projects/learning-module/a.js'].exports?和第一次?require?指向的是同一個(gè)?Object

          {?
          ????'/Users/helkyle/projects/learning-module/b.js':?
          ???????Module?{
          ?????????id:?'.',
          ?????????exports:?{},
          ?????????parent:?null,
          ?????????filename:?'/Users/helkyle/projects/learning-module/b.js',
          ?????????loaded:?false,
          ?????????children:?[?[Object]?],
          ?????????paths:?
          ??????????[?'/Users/helkyle/projects/learning-module/node_modules',
          ????????????'/Users/helkyle/projects/node_modules',
          ????????????'/Users/helkyle/node_modules',
          ????????????'/Users/node_modules',
          ????????????'/node_modules'?]?},
          ??'/Users/helkyle/projects/learning-module/a.js':?
          ???????Module?{
          ?????????id:?'/Users/helkyle/projects/learning-module/a.js',
          ?????????exports:?{?foo:?1?},
          ?????????parent:?
          ??????????Module?{
          ????????????id:?'.',
          ????????????exports:?{},
          ????????????parent:?null,
          ????????????filename:?'/Users/helkyle/projects/learning-module/b.js',
          ????????????loaded:?false,
          ????????????children:?[Array],
          ????????????paths:?[Array]?},
          ?????????filename:?'/Users/helkyle/projects/learning-module/a.js',
          ?????????loaded:?true,
          ?????????children:?[],
          ?????????paths:?[?
          ????????????'/Users/helkyle/projects/learning-module/node_modules',
          ????????????'/Users/helkyle/projects/node_modules',
          ????????????'/Users/helkyle/node_modules',
          ????????????'/Users/node_modules',
          ????????????'/node_modules'?
          ????????]
          ???}
          }

          應(yīng)用——實(shí)現(xiàn) Jest 的 mock module 效果

          jest??是 Facebook 開源的前端測(cè)試庫(kù),提供了很多非常強(qiáng)大又實(shí)用的功能。mock module?是其中非常搶眼的特性。使用方式是在需要被 mock 的文件模塊同級(jí)目錄下的?__mock__?文件夾添加同名文件,執(zhí)行測(cè)試代碼時(shí)運(yùn)行?jest.mock(modulePath),jest?會(huì)自動(dòng)加載?mock?版本的?module。舉個(gè)例子,項(xiàng)目中有個(gè) apis 文件,提供對(duì)接后端 api。

          //?/projects/foo/apis.js
          module.export?=?{
          ????getUsers:?()?=>?fetch('api/users')
          };

          在跑測(cè)試過程中,不希望它真的連接后端請(qǐng)求。這時(shí)候根據(jù) jest 文檔,在 apis 文件同級(jí)目錄創(chuàng)建?mock file

          //?/projects/foo/__mock__/apis.js
          module.exports?=?{
          ????getUsers:?()?=>?[
          ????????{
          ????????????id:?"1",
          ????????????name:?"Helkyle"
          ????????},
          ????????{
          ????????????id:?"2",
          ????????????name:?"Chinuketsu"
          ????????}
          ????]
          }

          測(cè)試文件中,主動(dòng)調(diào)用 jest.mock('./apis.js') 即可。

          jest.mock('./apis.js');
          const?apis?=?require('./apis.js');

          apis.getUsers()
          ??.then((users)?=>?{
          ????console.log(users);
          ????//?[?{?id:?'1',?name:?'Helkyle'?},?{?id:?'2',?name:?'Chinuketsu'?}?]
          ??})

          了解?require?的基礎(chǔ)原理之后,我們也來(lái)實(shí)現(xiàn)類似的功能,將加載 api.js 的語(yǔ)句改寫成加載?mock/api.js。

          使用 require.cache

          由于緩存機(jī)制的存在,提前寫入目標(biāo)緩存,再次 require 將得到我們期望的結(jié)果。

          //?提前 require mock apis 文件,產(chǎn)生緩存。
          require('./__mock__/apis.js');

          //?給即將 require 的文件路徑寫入緩存
          const?originalPath?=?require.resolve('./apis.js');
          require.cache[originalPath]?=?require.cache[require.resolve('./__mock__/apis.js')];

          //?得到的將是緩存版本
          const?apis?=?require('./apis.js');

          apis.getUsers()
          ??.then((users)?=>?{
          ????console.log(users);
          ????//?[?{?id:?'1',?name:?'Helkyle'?},?{?id:?'2',?name:?'Chinuketsu'?}?]
          ??})

          魔改 module._load

          基于?require.cache?的方式,需要提前?require mock module。?提到了,由于最終都是通過?Module._load來(lái)加載模塊,在這個(gè)位置進(jìn)行攔截即可完成按需?mock

          const?Module?=?require('module');
          const?originalLoad?=?Module._load;

          Module._load?=?function?(path,?...rest)?{
          ??if?(path?===?'./apis.js')?{
          ????path?=?'./__mock__/apis.js';
          ??}
          ??return?originalLoad.apply(Module,?[path,?...rest]);
          }

          const?apis?=?require('./apis.js');
          apis.getUsers()
          ??.then((users)?=>?{
          ????console.log(users);
          ??})

          注意:以上內(nèi)容僅供參考。從實(shí)際運(yùn)行結(jié)果上看,Jest?有自己實(shí)現(xiàn)的模塊加載機(jī)制,跟?commonjs?有出入。比如在?jest?中?require module?并不會(huì)寫入?require.cache

          程序啟動(dòng)時(shí)的?require

          查閱?Node?文檔發(fā)現(xiàn),在?Command Line?章節(jié)也有一個(gè)?--require?,使用這個(gè)參數(shù)可以在執(zhí)行業(yè)務(wù)代碼之前預(yù)先加載特定模塊。舉個(gè)例子,編寫?setup?文件,往?global?對(duì)象上掛載?it,?assert?等方法。

          //?setup.js
          global.it?=?async?function?test(title,?callback)?{
          ??try?{
          ????await?callback();
          ????console.log(`??${title}`);
          ??}?catch?(error)?{
          ????console.error(`??${title}`);
          ????console.error(error);
          ??}
          }
          global.assert?=?require('assert');

          給啟動(dòng)代碼添加?--require?參數(shù)。引入?global.assert,?global.it,就可以在代碼中直接使用?assert, it?不用在測(cè)試文件中引入。

          node?--require?'./setup.js'?foo.test.js
          //?foo.test.js
          //?不需要?require('assert');
          function?sum?(a,?b)?{
          ????return?a?+?b;
          }

          //?沒有?--require 會(huì)報(bào) it is not defined
          it('add?two?numbers',?()?=>?{
          ????assert(sum(2,?3)?===?5);
          })
          - END -



          分享前端好文,點(diǎn)亮?在看



          瀏覽 58
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  能看的肏屄视频 | 激情瑟瑟 | 亚洲精选一区二区三区 | 国产99视频在线观看 | 豆花成人无码视频在线 |