核心要点
本文由Moritz Kröger和Tom Greco审核。感谢所有SitePoint的同行评审员,使SitePoint的内容达到最佳状态!
您是否曾经使用过第三方代码,除了一个让您抓狂的小问题外,其他都运行良好?创建者为什么忘记删除那些讨厌的控制台日志?如果那个API调用可以多做一件事,那不是很好吗?如果是这样,那么您就会知道,让维护者实施您的更改可能很困难(或不可能)。但是,自己更改代码呢?如果您没有源代码并且不想自己托管它们,该如何操作?欢迎来到JavaScript猴子补丁的世界之旅!
在本文中,我们将了解什么是猴子补丁,并逐步完成一些不同的示例,使用它来更改第三方小部件的功能以适应我们的需求。
什么是猴子补丁?
猴子补丁(以下简称MP)是一种技术,用于覆盖、扩展甚至抑制代码段的默认行为,而无需更改其原始源代码。这是通过用修复版本替换原始行为来实现的。
本文将使用现有的反馈框小部件,该小部件显示一个简单的、可滑动的弹出窗口(如下图所示),其中包含反馈表单。
源代码已修改为包含用作MP目标的用例。目标是指我们将要修补的特定功能、特性或最低级别的使用方法。
我做的另一个修改是删除了围绕代码的立即调用函数表达式(IIFE)。这样做是为了专注于MP的技术。
您可以在Plunker中找到整个示例,包括本文中讨论的猴子补丁。
猴子补丁不是一种不好的实践吗?
在开始之前,让我们先明确一点:是的,MP被认为是一种不好的实践——邪恶的eval、命令式编程、可变数据结构、双向绑定等等也是如此。
如果您使用其中任何一种,很可能会有相当大的一群人告诉您您做错了,应该更改此或那项以适应更好的条件。但一如既往,有不同的工具和技术可用,它们在特定场景下的适用性各不相同。有时,看起来极端、疯狂或根本不好的东西可能是特定情况下的最后手段。不幸的是,由于某些实践被认为是不好的,您甚至找不到很多文章来描述如何以正确的方式做错事。
此处描述的情况可能是不自然的,用虚假的小部件将其推向极端,以显示您的选择。然后,作为读者,您必须决定是否喜欢您看到的内容。如果没有什么别的,在阅读本文之后,您将有更好的理解,以便反对MP。
猴子补丁的目标
在我们深入研究这些技术之前,让我们首先检查一下我们想要实现的目标。修改后的窗口部件有一些代码异味,我们想解决这些问题。
第一个是名为toggleError的方法,该方法应该根据布尔参数更改元素的背景颜色
FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }
如您所见,它通过jQuery方法css设置background-color属性。这是一个问题,因为我们希望通过样式表规则指定它。
在开发小部件时,使用控制台日志来向开发人员提示当前正在执行的内容。在开发过程中这可能是一种不错的方法,但在生产使用中肯定不是最好的方法。因此,我们需要找到一种方法来删除所有这些调试语句。
该小部件很棒,但它有一个奇怪的行为。每次初始化脚本时,它都会向一个奇怪的广告服务器发出请求,并在我们的页面上显示不必要的膨胀内容。
FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ...
注意:演示代码针对Plunker中的JSON文件来模拟传出的Ajax请求,但我希望您明白这一点。
覆盖方法
MP的关键概念之一是获取现有函数并使用自定义行为在调用原始代码之前或之后对其进行增强。但调用原始实现并非总是必要的,因为有时您只想用自定义操作替换它。这种方法非常适合帮助我们解决硬编码的背景颜色问题。
应用MP的位置必须在加载并提供原始实现之后。通常,您应该努力使更改尽可能接近目标,但请记住,目标的实现可能会随着时间的推移而发生变化。至于我们的示例,初始化以及MP将进入文件main.js。
查看小部件实现,我们可以看到有一个FeedbackBox对象作为小部件的根。稍后,将在其原型上实现toggleError函数。
FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }
由于JavaScript是一种动态语言,其对象可以在运行时修改,因此我们最终将做的只是用我们的自定义方法替换toggleError。唯一需要注意的是保持签名(名称和传递的参数)相同。
FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ...
新的实现现在只需向给定的元素添加一个错误类,从而允许我们通过css设置背景颜色。
增强方法
在前面的示例中,我们看到了如何通过提供我们自己的方法来覆盖原始实现。另一方面,处理控制台日志应该只是过滤掉特定的调用并抑制它们。成功的关键是检查您嵌入的代码并尝试理解其工作流程。通常,这是通过启动您选择的浏览器中的开发者控制台并在加载的资源中窥视、添加断点和调试目标代码部分来完成的,以便了解它的功能。但是,这一次,您只需在另一个选项卡中打开名为vendor/jquery.feedBackBox.js的Plunker示例中的实现即可。
通过查看调试消息,我们可以看到它们中的每一个都以FeedbackBox:开头。因此,实现我们想要的目标的一种简单方法是拦截原始调用,检查要写入的提供的文本,并且仅当它不包含调试提示时才调用原始方法。
为此,让我们首先将原始console.log存储到一个变量中以供以后使用。然后,我们再次用我们的自定义实现覆盖原始实现,该实现首先检查提供的属性文本是否为字符串类型,如果是,则检查它是否包含子字符串FeedbackBox:。如果是,我们将什么也不做,否则我们将通过调用其apply方法来执行原始控制台代码。
请注意,此方法将上下文作为第一个参数,这意味着应该在该对象上调用该方法,以及一个神奇的arguments变量。后者是最初传递给原始console.log调用的所有参数的数组。
function FeedbackBox(elem, options) { this.options = options; this.element = elem; this.isOpen = false; } FeedbackBox.prototype.toggleError = function(obj, isError) { ... }
注意:您可能想知道为什么我们没有简单地转发text属性。好吧,console.log实际上可以用无限的参数调用,最终这些参数将连接到单个文本输出。因此,与其定义所有这些参数(对于无限的可能性来说可能非常困难),我们只需转发所有传入的内容。
拦截Ajax调用
最后但并非最不重要的一点是,让我们看看如何解决广告服务器问题。让我们再次查看小部件的init函数:
FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.css("background-color", "darkgrey"); } else { obj.css("background-color", ""); } }
第一个想法可能是打开浏览器并搜索如何覆盖jQuery插件。根据您的搜索技能的好坏,您可能会或可能不会找到合适的答案。但是,让我们停下来思考一下这里到底发生了什么。无论jQuery对其ajax方法做了什么,它最终都会在某个时候创建一个本机XMLHttpRequest。
让我们看看它在幕后是如何工作的。在MDN上找到的最简单的示例向我们展示了这一点:
FeedbackBox.prototype.init = function() { // 我们想要跳过的广告服务器调用 $.ajax('vendor/service.json', { method: 'GET' }).then(function(data) { console.log("FeedbackBox: AdServer contacted"); }); ...
我们看到创建了一个新的XMLHttpRequest实例。它有一个onreadystatechange方法,我们实际上并不关心,然后是open和send方法。太好了。所以我们的想法是猴子补丁send方法并告诉它不要执行对特定URL的调用。
function FeedbackBox(elem, options) { this.options = options; this.element = elem; this.isOpen = false; } FeedbackBox.prototype.toggleError = function(obj, isError) { ... }
好吧,事实证明您无法从对象本身获取目标URL。糟糕。那我们该怎么办?我们将其放在对象上。寻找获取URL的第一个机会,我们可以看到open方法将其作为第二个参数接受。为了使URL在对象本身可用,让我们首先MP open方法。
和以前一样,我们将原始open方法存储在一个变量中以供以后使用。然后我们用自定义实现覆盖原始实现。由于我们可以使用JavaScript(一种动态语言),因此我们可以随时创建一个新属性并将其命名为_url,该属性将设置为传入参数的值。
FeedbackBox.prototype.toggleError = function(obj, isError) { if(isError) { obj.addClass("error"); } else { obj.removeClass("error"); } };
除此之外,我们调用原始open方法,不做任何其他操作。
重新审视我们的send MP,现在很明显如何解决条件检查。以下是修改后的版本:
var originalConsoleLog = console.log; console.log = function(text) { if (typeof text === "string" && text.indexOf("FeedbackBox:") === 0) { return; } originalConsoleLog.apply(console, arguments); }
结论
我们在这里看到的是关于使用猴子补丁在运行时更改代码行为的简短介绍。但更重要的是,我希望这篇文章能够让您了解如何处理猴子补丁问题。虽然补丁本身通常很简单,但重要的是如何在运行时调整代码的想法。
此外,我希望无论您对猴子补丁有何看法,您都有机会看到使用动态语言的美妙之处,它允许您动态地在运行时更改甚至本机实现。
关于实用猴子补丁的常见问题解答 (FAQ)
JavaScript中的猴子补丁是一种技术,其中内置对象或用户定义对象的行为被修改,通常是通过添加、修改或更改对象的原型来实现的。这是一种扩展或更改代码行为而不更改原始源代码的方法。此技术可用于实施修复、增强现有函数,甚至用于测试和调试目的。
虽然JavaScript和Python中的猴子补丁的概念相同——修改或扩展对象的行为——但由于语言本身的差异,实现方式有所不同。在JavaScript中,猴子补丁通常是通过修改对象的原型来完成的,而在Python中,它是通过添加或更改类或实例方法来完成的。这两种语言的灵活性都允许进行猴子补丁,但应谨慎使用此技术,以避免出现意外行为。
猴子补丁是一个强大的工具,但它并非没有争议。虽然它可以快速修改或扩展功能而无需更改原始源代码,但它也可能导致不可预测的行为和冲突,尤其是在过度使用或使用不当时。因此,通常建议谨慎和负责任地使用猴子补丁,并始终考虑对整个代码库的潜在影响。
猴子补丁的主要风险是它可能导致代码中出现不可预测的行为和冲突。因为它修改了现有对象的行为,所以如果在代码库的其他地方使用了已修补的方法,它可能会破坏代码。它还可能导致其他开发人员感到困惑,他们可能不知道这些修改。因此,务必清晰而全面地记录任何猴子补丁。
要在JavaScript中干净地猴子补丁一个函数,您可以围绕原始函数创建一个包装器。此包装器函数将调用原始函数,然后根据需要添加或修改行为。这样,原始函数保持不变,附加行为清晰地分开,使代码更易于理解和维护。
是的,猴子补丁可以作为测试和调试的有用工具。通过修改或扩展函数或方法的行为,您可以模拟不同的场景、注入错误或添加日志来跟踪代码的执行。但是,重要的是在生产代码中删除或隔离这些补丁,以避免任何意外的副作用。
在JavaScript中,原型在猴子补丁中起着至关重要的作用。由于JavaScript是一种基于原型的语言,因此每个对象都有一个原型,它从中继承属性和方法。通过修改对象的原型,您可以更改该对象所有实例的行为。这是JavaScript中猴子补丁的基础。
猴子补丁对JavaScript性能的影响通常很小。但是,过度或不当使用猴子补丁可能会导致性能问题。例如,如果在代码中频繁使用已修补的方法,则附加行为可能会减慢执行速度。因此,务必谨慎使用猴子补丁并定期监控性能。
是的,猴子补丁可以用来扩展内置的JavaScript对象。通过修改内置对象的原型,您可以添加新的方法或属性,这些方法或属性将可用于该对象的所有实例。但是,应谨慎执行此操作,以避免与未来版本的JavaScript发生冲突,这些版本可能会引入相同的方法或属性。
JavaScript中猴子补丁有几种替代方案。一种常见的方法是使用组合,您创建一个包含原始对象并添加或覆盖行为的新对象。另一种方法是使用继承,您创建一个从原始类继承并覆盖方法的新类。这些方法可以提供与猴子补丁类似的灵活性,但具有更好的封装性和更少的冲突风险。
以上是JavaScript中猴子补丁的务实用途的详细内容。更多信息请关注PHP中文网其他相关文章!