js中執行到一個if就停止的代碼_Node 中如何引入一個模塊及其細節

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

const fs = require('fs')const add = (x, y) => x + ymodule.exports = add

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

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

module wrapper

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

(function(exports, require, module, __filename, __dirname) {// 所有的模塊代碼都被包裹在這個函數中const fs = require('fs')const add = (x, y) => x + ymodule.exports = add
});

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

  • exports
  • require
  • module
  • __filename
  • __dirname

7c6c5844b85d66d5e8dd71b6fc159270.png

module

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

const fs = require('fs')const add = (x, y) => x + ymodule.exports = addconsole.log(module)

f834c54371c79c1819dd7ac4007b8b61.png
  • module.id: 如果是 . 代表是入口模塊,否則是模塊所在的文件名,可見如下的 koa
  • module.exports: 模塊的導出

e2ec351eaf92b0d6c8f99f2b31e5c494.png

module.exports 與 exports

? module.exports 與 exports 有什么關系?
?

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

  • exports: 實際上是 module.exports 的引用
  • require: 大多情況下是 Module.prototype.require
  • module
  • __filename
  • __dirname: path.dirname(__filename)
// <node_internals>/internal/modules/cjs/loader.js:1138Module.prototype._compile = function(content, filename) {// ...const dirname = path.dirname(filename);const require = makeRequireFunction(this, redirects);let result;// 從中可以看出:exports = module.exportsconst 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 進行調試,可以發現 require 是一個極其復雜的對象

dbfa5cc6a66f031150ef5c6e56a442e4.png

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

// <node_internals>/internal/modules/cjs/helpers.js:33function makeRequireFunction(mod, redirects) {const Module = mod.constructor;let require;if (redirects) {// ...} else {// require 實際上是 Module.prototype.requirerequire = 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;
}
? 關于 require 更詳細的信息可以去參考官方文檔: Node API: require
?

require(id)

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

// <node_internals>/internal/modules/cjs/loader.js:1019Module.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 載入,大致的總結如下:

  1. 如果 Module._cache 命中模塊緩存,則直接取出 module.exports,加載結束
  2. 如果是 NativeModule,則 loadNativeModule 加載模塊,如 fshttppath 等模塊,加載結束
  3. 否則,使用 Module.load 加載模塊,當然這個步驟也很長,下一章節再細講
// <node_internals>/internal/modules/cjs/loader.js:879Module._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

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

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

// <node_internals>/internal/modules/cjs/loader.js:899require.cache = Module._cache;

這里有個小測試:

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

「index.js」

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

「utils.js」

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

「答案是只執行了一次」,因此 require.cache,在 index.js 末尾打印 require,此時會發現一個模塊緩存

// index.jsrequire('./utils')
require('./utils')console.log(require)

b3425ca9dde5c0c1e4d904791839612b.png

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

? 如何不重啟應用熱加載模塊呢?
?

答:「刪掉 Module._cache,但同時會引發問題,如這種 一行 delete require.cache 引發的內存泄漏血案

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

總結

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

關注我

? 本文收錄于 GitHub 山月行博客: shfshanyue/blog,內含我在實際工作中碰到的問題、關于業務的思考及在全棧方向上的學習
  • 前端工程化系列
  • Node進階系列

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/534310.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/534310.shtml
英文地址,請注明出處:http://en.pswp.cn/news/534310.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

二級MS Office公共基礎知識錯題本(1)

1&#xff0c;順序程序具有順序性、封閉性和可再現性的特點&#xff0c;不具備并發性 2&#xff0c;為了降低算法的空間復雜度&#xff0c;主要應減少輸入數據所占的存儲空間以及額外空間&#xff0c;通常采用壓編存儲技術。 3&#xff0c;樹的總的結點數為樹中所有結點的度數…

c++ file* 句柄泄漏_C++核心指南:P.8 勿泄漏任務資源

P.8: 勿泄漏任務資源原因隨著時間的推移&#xff0c;即使是資源的緩慢增長也會耗盡這些資源的可用性&#xff0c;這對于長時間運行的程序特別重要&#xff0c;但也是負責任的編程行為的基本部分。糟糕的例子void f(char* name){ FILE* input fopen(name, "r"); // .…

數據規范化、實體-聯系圖、狀態轉換圖、層次方框圖、Warnier圖、IPO圖及驗證軟件需求

數據規范化 軟件系統經常使用各種長期保存的信息&#xff0c;這些信息通常以一定方式組織并存儲在數據庫或文件中&#xff0c;為減少數據冗余&#xff0c;避免出現插入異常或刪除異常&#xff0c;簡化修改數據的過程,通常需要把數據結構規范化。 通常用“范式(normal forms)”…

python和c混合編程 gil,如何在python中使用C擴展來解決GIL

I want to run a cpu intensive program in Python across multiple cores and am trying to figure out how to write C extensions to do this. Are there any code samples or tutorials on this?解決方案You can already break a Python program into multiple processes.…

Linux基礎(iptables與firewalld防火墻)

iptables 在早期的Linux系統中&#xff0c;默認使用的是iptables防火墻管理服務來配置防火墻。盡管新型的fierwalld防火墻管理服務已經被投入使用多年&#xff0c;但是大量的企業在生產環境中依然出于各種原因而繼續使用iptables。 策略與規則鏈 防火墻會從上至下的順序來讀…

虛擬跳線軟件干什么用的_瘋狂刷單!用違法軟件生成虛擬手機號,“騎手”半年“刷單”牟利60余萬,百米內竟有萬筆訂單 | 申晨間...

來源&#xff1a;新聞晨報 記者&#xff1a;吳藝璇借助違法軟件生成虛擬手機號碼&#xff0c;利用平臺審核漏洞大量注冊用戶&#xff0c;大量“刷單”騙取平臺的返現和購物補貼&#xff0c;半年內瘋狂刷1.8萬余單&#xff0c;累計牟利60余萬元。近日&#xff0c;在市公安局刑偵…

軟件工程(總體設計①設計過程)

經過需求分析&#xff08;https://blog.csdn.net/weixin_45626468/article/details/115324885&#xff09;階段的工作&#xff0c;系統必選“做什么”已經清楚了&#xff0c;現在是決定“怎樣做”的時候了。 總體設計的基本目的就是回答“概況地說&#xff0c;系統應該如何實現…

ygo游戲王卡組_ACG大科普(7)游戲王

大家是否在小時候接觸過一種卡片類似這種的 這就是今天的主角游戲王。 背景 1996年&#xff0c;《游戲王》漫畫開始在集英社《周刊少年Jump》連載。 1998年&#xff0c;Bandai推出以《游戲王》原作中登場的集換卡牌游戲“M&W”為題材的集換卡牌。 采用Bandai的卡片自動販賣…

Qt圖形界面編程入門(基本窗口及控件)

基本窗口類QWidget QWidget是所有窗體部件的基類&#xff0c;例如對話框類&#xff0c;主窗體類&#xff0c;以及其他諸如按鈕&#xff0c;編輯框&#xff0c;標簽等等都是由QWidget派生得到&#xff0c;QWidget擁有的方法往往都可以在其他子類中使用。 窗體的幾何尺寸分為包…

背景se_盤點那些RPG手游中主角的背景故事,越悲情越強大

RPG游戲一直以代入感超強的游戲方式來吸引玩家&#xff0c;用超越現實的藝術手段把玩家帶入到虛擬的游戲世界&#xff0c;讓玩家擔任不同的社會角色來去經歷不同的虛擬故事&#xff0c;體驗多種人生經歷&#xff0c;想要扮演任何角色都是有可能的。當然在RPG游戲中也有好壞之分…

TensorFlow構建二維數據擬合模型(2)

變量的定義和使用 變量的定義與初始化 TensorFlow中&#xff0c;變量是一種特殊的張量&#xff0c;其值可以是一個任意類型的形狀的張量。 與其他張量不同&#xff0c;變量存在于單個回話調用的上下文之外&#xff0c;主要作用是保存和更新模型中的參數。 聲明變量通常使用…

c++用牛頓法開多次根_望遠鏡的歷史之三:大神出世,改變望遠鏡歷史的竟然是牛頓...

上次我們說到格里高利望遠鏡有點畫蛇添足&#xff0c;那么格里高利望遠鏡添了什么呢&#xff1f;格里高利望遠鏡格里高利望遠鏡觀測的圖像都是正立的&#xff0c;這就意味著要采用多個凹面反射鏡&#xff0c;而當時凹面反射鏡磨制不易&#xff0c;無論是多大的科學家都要親自動…

python浮點型精度損失問題_解決float型數據精度損失問題

問題&#xff1a;浮點型數據存儲方式會導致數據精度損失&#xff0c;增大計算誤差。float fval 0.45;  // 單步調試發現其真實值為&#xff1a;0.449999988double dval 0.45; // 單步調試發現其真實值為&#xff1a;0.45000000000000001當很多個這樣的單精度浮點型數據進行…

Linux配置本地yum源(RHEL8)

https://www.cnblogs.com/itwangqiang/p/13391401.html

如何把照片正面變成反面_各國簽證照片要求大全 (含模板)

對于不是很熟悉簽證的小伙伴來說&#xff0c;面對全球那么多國家的簽證而且每張簽證照片的規格不同為此我們為您整理了各國簽證照片要求大全 東南亞國家的簽證照要求基本相同&#xff0c;就以泰國為例&#xff0c;告訴大家簽證照的注意事項。“泰國&#xff0c;新加坡&#xff…

TensorFlow實驗(3)

模型的保存與恢復 我們來簡單實現一下模型的保存與恢復 訓練完TensorFlow模型后&#xff0c;可將其保存為文件&#xff0c;以便于預測新數據時直接加載使用。 TensorFlow模型主要包含網絡的設計或者圖以及已經訓練好的網絡參數的值。 TensorFlow提供的tf.train.Saver()函數…

ad域 禁用賬號_IST-AD域信息同步平臺來襲

IST的AD域信息同步系統是能幫助域管理員簡化日常的一些管理工作&#xff0c;可以讓AD域系統與其他的業務系統進行用戶信息同步&#xff0c;實現自動的新舊用戶帳戶信息的同步修改、組織架構同步調整&#xff0c;并有簡單易操作的配置頁面系統與操作日志查詢等。通過ODBC、Web S…

Linux基礎(firewalld防火墻配置管理工具的圖形用戶界面)

firewall-config的界面如圖所示 我們先將當前區域中請求http服務的流量設置為允許&#xff0c;但僅限當前生效。具體配置如圖 嘗試添加一條防火墻策略規則&#xff0c;使其放行訪問8080-8088端口&#xff08;TCP協議&#xff09;的流量&#xff0c;并將其設置為永久生效&#x…

ios 請求失敗封裝_vue_axios請求封裝、異常攔截統一處理

1、前端網絡請求封裝、異常統一處理vue中采用axios處理網絡請求&#xff0c;避免請求接口重復代碼&#xff0c;以及各種網絡情況造成的異常情況的判斷&#xff0c;采用axios請求封裝和異常攔截操作&#xff1b;axios 請求封裝// 引入axios文件包import axios from axios// POST…

Linux基礎(使用ssh服務管理遠程主機1)

配置網絡參數 使用nmtui命令配置網絡參數&#xff0c;以及通過nmcli命令查看網絡信息并管理網絡會話服務。 執行nmtui命令運行網絡配置工具 進入主界面 選中編輯連接并按下回車鍵 選中要編輯的網卡名稱&#xff0c;然后按下Edit&#xff08;編輯&#xff09;按鈕 把網絡IPv4 …