核心要點
本文由Moritz Kröger和Tom Greco審核。感謝所有SitePoint的同行評審員,使SitePoint的內容達到最佳狀態!
您是否曾經使用過第三方代碼,除了一個讓您抓狂的小問題外,其他都運行良好?創建者為什麼忘記刪除那些討厭的控制台日誌?如果那個API調用可以多做一件事,那不是很好嗎?如果是這樣,那麼您就會知道,讓維護者實施您的更改可能很困難(或不可能)。但是,自己更改代碼呢?如果您沒有源代碼並且不想自己託管它們,該如何操作?歡迎來到JavaScript猴子補丁的世界之旅!
在本文中,我們將了解什麼是猴子補丁,並逐步完成一些不同的示例,使用它來更改第三方小部件的功能以適應我們的需求。
什麼是猴子補丁?
猴子補丁(以下簡稱MP)是一種技術,用於覆蓋、擴展甚至抑制代碼段的默認行為,而無需更改其原始源代碼。這是通過用修復版本替換原始行為來實現的。
本文將使用現有的反饋框小部件,該小部件顯示一個簡單的、可滑動的彈出窗口(如下圖所示),其中包含反饋表單。
源代碼已修改為包含用作MP目標的用例。目標是指我們將要修補的特定功能、特性或最低級別的使用方法。
我做的另一個修改是刪除了圍繞代碼的立即調用函數表達式(IIFE)。這樣做是為了專注於MP的技術。
您可以在Plunker中找到整個示例,包括本文中討論的猴子補丁。
猴子補丁不是一種不好的實踐嗎?
在開始之前,讓我們先明確一點:是的,MP被認為是一種不好的實踐——邪惡的eval、命令式編程、可變數據結構、雙向綁定等等也是如此。
如果您使用其中任何一種,很可能會有相當大的一群人告訴您您做錯了,應該更改此或那項以適應更好的條件。但一如既往,有不同的工具和技術可用,它們在特定場景下的適用性各不相同。有時,看起來極端、瘋狂或根本不好的東西可能是特定情況下的最後手段。不幸的是,由於某些實踐被認為是不好的,您甚至找不到很多文章來描述如何以正確的方式做錯事。
此處描述的情況可能是不自然的,用虛假的小部件將其推向極端,以顯示您的選擇。然後,作為讀者,您必須決定是否喜歡您看到的內容。如果沒有什麼別的,在閱讀本文之後,您將有更好的理解,以便反對MP。
猴子補丁的目標
在我們深入研究這些技術之前,讓我們首先檢查一下我們想要實現的目標。修改後的窗口部件有一些代碼異味,我們想解決這些問題。
第一個是名為toggleError的方法,該方法應該根據布爾參數更改元素的背景顏色
<code class="language-javascript">FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }</code>
如您所見,它通過jQuery方法css設置background-color屬性。這是一個問題,因為我們希望通過樣式表規則指定它。
在開發小部件時,使用控制台日誌來向開發人員提示當前正在執行的內容。在開發過程中這可能是一種不錯的方法,但在生產使用中肯定不是最好的方法。因此,我們需要找到一種方法來刪除所有這些調試語句。
該小部件很棒,但它有一個奇怪的行為。每次初始化腳本時,它都會向一個奇怪的廣告服務器發出請求,並在我們的頁面上顯示不必要的膨脹內容。
<code class="language-javascript">FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ... </code>
注意:演示代碼針對Plunker中的JSON文件來模擬傳出的Ajax請求,但我希望您明白這一點。
覆蓋方法
MP的關鍵概念之一是獲取現有函數並使用自定義行為在調用原始代碼之前或之後對其進行增強。但調用原始實現並非總是必要的,因為有時您只想用自定義操作替換它。這種方法非常適合幫助我們解決硬編碼的背景顏色問題。
應用MP的位置必須在加載並提供原始實現之後。通常,您應該努力使更改盡可能接近目標,但請記住,目標的實現可能會隨著時間的推移而發生變化。至於我們的示例,初始化以及MP將進入文件main.js。
查看小部件實現,我們可以看到有一個FeedbackBox對像作為小部件的根。稍後,將在其原型上實現toggleError函數。
<code class="language-javascript">FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }</code>
由於JavaScript是一種動態語言,其對象可以在運行時修改,因此我們最終將做的只是用我們的自定義方法替換toggleError。唯一需要注意的是保持簽名(名稱和傳遞的參數)相同。
<code class="language-javascript">FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ... </code>
新的實現現在只需向給定的元素添加一個錯誤類,從而允許我們通過css設置背景顏色。
增強方法
在前面的示例中,我們看到瞭如何通過提供我們自己的方法來覆蓋原始實現。另一方面,處理控制台日誌應該只是過濾掉特定的調用並抑制它們。成功的關鍵是檢查您嵌入的代碼並嘗試理解其工作流程。通常,這是通過啟動您選擇的瀏覽器中的開發者控制台並在加載的資源中窺視、添加斷點和調試目標代碼部分來完成的,以便了解它的功能。但是,這一次,您只需在另一個選項卡中打開名為vendor/jquery.feedBackBox.js的Plunker示例中的實現即可。
通過查看調試消息,我們可以看到它們中的每一個都以FeedbackBox:開頭。因此,實現我們想要的目標的一種簡單方法是攔截原始調用,檢查要寫入的提供的文本,並且僅當它不包含調試提示時才調用原始方法。
為此,讓我們首先將原始console.log存儲到一個變量中以供以後使用。然後,我們再次用我們的自定義實現覆蓋原始實現,該實現首先檢查提供的屬性文本是否為字符串類型,如果是,則檢查它是否包含子字符串FeedbackBox:。如果是,我們將什麼也不做,否則我們將通過調用其apply方法來執行原始控制台代碼。
請注意,此方法將上下文作為第一個參數,這意味著應該在該對像上調用該方法,以及一個神奇的arguments變量。後者是最初傳遞給原始console.log調用的所有參數的數組。
<code class="language-javascript">function FeedbackBox(elem, options) { this.options = options; this.element = elem; this.isOpen = false; } FeedbackBox.prototype.toggleError = function(obj, isError) { ... }</code>
注意:您可能想知道為什麼我們沒有簡單地轉發text屬性。好吧,console.log實際上可以用無限的參數調用,最終這些參數將連接到單個文本輸出。因此,與其定義所有這些參數(對於無限的可能性來說可能非常困難),我們只需轉發所有傳入的內容。
攔截Ajax調用
最後但並非最不重要的一點是,讓我們看看如何解決廣告服務器問題。讓我們再次查看小部件的init函數:
<code class="language-javascript">FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }</code>
第一個想法可能是打開瀏覽器並蒐索如何覆蓋jQuery插件。根據您的搜索技能的好壞,您可能會或可能不會找到合適的答案。但是,讓我們停下來思考一下這裡到底發生了什麼。無論jQuery對其ajax方法做了什麼,它最終都會在某個時候創建一個本機XMLHttpRequest。
讓我們看看它在幕後是如何工作的。在MDN上找到的最簡單的示例向我們展示了這一點:
<code class="language-javascript">FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ... </code>
我們看到創建了一個新的XMLHttpRequest實例。它有一個onreadystatechange方法,我們實際上並不關心,然後是open和send方法。太好了。所以我們的想法是猴子補丁send方法並告訴它不要執行對特定URL的調用。
<code class="language-javascript">function FeedbackBox(elem, options) { this.options = options; this.element = elem; this.isOpen = false; } FeedbackBox.prototype.toggleError = function(obj, isError) { ... }</code>
好吧,事實證明您無法從對象本身獲取目標URL。糟糕。那我們該怎麼辦?我們將其放在對像上。尋找獲取URL的第一個機會,我們可以看到open方法將其作為第二個參數接受。為了使URL在對象本身可用,讓我們首先MP open方法。
和以前一樣,我們將原始open方法存儲在一個變量中以供以後使用。然後我們用自定義實現覆蓋原始實現。由於我們可以使用JavaScript(一種動態語言),因此我們可以隨時創建一個新屬性並將其命名為_url,該屬性將設置為傳入參數的值。
<code class="language-javascript">FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.addClass("error"); } else { obj.removeClass("error"); } };</code>
除此之外,我們調用原始open方法,不做任何其他操作。
重新審視我們的send MP,現在很明顯如何解決條件檢查。以下是修改後的版本:
<code class="language-javascript">var originalConsoleLog = console.log; console.log = function(text) { if (typeof text === "string" && text.indexOf("FeedbackBox:") === 0) { return; } originalConsoleLog.apply(console, arguments); }</code>
結論
我們在這裡看到的是關於使用猴子補丁在運行時更改代碼行為的簡短介紹。但更重要的是,我希望這篇文章能夠讓您了解如何處理猴子補丁問題。雖然補丁本身通常很簡單,但重要的是如何在運行時調整代碼的想法。
此外,我希望無論您對猴子補丁有何看法,您都有機會看到使用動態語言的美妙之處,它允許您動態地在運行時更改甚至本機實現。
關於實用猴子補丁的常見問題解答 (FAQ)
JavaScript中的猴子補丁是一種技術,其中內置對像或用戶定義對象的行為被修改,通常是通過添加、修改或更改對象的原型來實現的。這是一種擴展或更改代碼行為而不更改原始源代碼的方法。此技術可用於實施修復、增強現有函數,甚至用於測試和調試目的。
雖然JavaScript和Python中的猴子補丁的概念相同——修改或擴展對象的行為——但由於語言本身的差異,實現方式有所不同。在JavaScript中,猴子補丁通常是通過修改對象的原型來完成的,而在Python中,它是通過添加或更改類或實例方法來完成的。這兩種語言的靈活性都允許進行猴子補丁,但應謹慎使用此技術,以避免出現意外行為。
猴子補丁是一個強大的工具,但它並非沒有爭議。雖然它可以快速修改或擴展功能而無需更改原始源代碼,但它也可能導致不可預測的行為和衝突,尤其是在過度使用或使用不當時。因此,通常建議謹慎和負責任地使用猴子補丁,並始終考慮對整個代碼庫的潛在影響。
猴子補丁的主要風險是它可能導致代碼中出現不可預測的行為和衝突。因為它修改了現有對象的行為,所以如果在代碼庫的其他地方使用了已修補的方法,它可能會破壞代碼。它還可能導致其他開發人員感到困惑,他們可能不知道這些修改。因此,務必清晰而全面地記錄任何猴子補丁。
要在JavaScript中乾淨地猴子補丁一個函數,您可以圍繞原始函數創建一個包裝器。此包裝器函數將調用原始函數,然後根據需要添加或修改行為。這樣,原始函數保持不變,附加行為清晰地分開,使代碼更易於理解和維護。
是的,猴子補丁可以作為測試和調試的有用工具。通過修改或擴展函數或方法的行為,您可以模擬不同的場景、注入錯誤或添加日誌來跟踪代碼的執行。但是,重要的是在生產代碼中刪除或隔離這些補丁,以避免任何意外的副作用。
在JavaScript中,原型在猴子補丁中起著至關重要的作用。由於JavaScript是一種基於原型的語言,因此每個對像都有一個原型,它從中繼承屬性和方法。通過修改對象的原型,您可以更改該對象所有實例的行為。這是JavaScript中猴子補丁的基礎。
猴子補丁對JavaScript性能的影響通常很小。但是,過度或不當使用猴子補丁可能會導致性能問題。例如,如果在代碼中頻繁使用已修補的方法,則附加行為可能會減慢執行速度。因此,務必謹慎使用猴子補丁並定期監控性能。
是的,猴子補丁可以用來擴展內置的JavaScript對象。通過修改內置對象的原型,您可以添加新的方法或屬性,這些方法或屬性將可用於該對象的所有實例。但是,應謹慎執行此操作,以避免與未來版本的JavaScript發生衝突,這些版本可能會引入相同的方法或屬性。
JavaScript中猴子補丁有幾種替代方案。一種常見的方法是使用組合,您創建一個包含原始對象並添加或覆蓋行為的新對象。另一種方法是使用繼承,您創建一個從原始類繼承並覆蓋方法的新類。這些方法可以提供與猴子補丁類似的靈活性,但具有更好的封裝性和更少的衝突風險。
以上是JavaScript中猴子補丁的務實用途的詳細內容。更多資訊請關注PHP中文網其他相關文章!