现在我能听到你喊叫:“为什么当有更好的工具可用时,你还想用 jQuery?简直是疯狂!你到底是什么样的疯子?” 这些都是合理的问题,我将通过一些背景信息来回答它们。
在我目前的工作中,我负责维护和更新一个遗留网站。 它很古老。 前端依赖于 jQuery,就像大多数旧的遗留系统一样,它并不处于最佳状态。 这本身并不是最糟糕的,但我正在与其他约束条件作斗争。 例如,我们正在进行系统的全面重写,因此大量重构工作没有得到批准,而且我未经全面安全审查也不得向现有系统添加新依赖项,这在历史上可能需要长达一年的时间。 有效地,jQuery 是我唯一可以使用的 JavaScript 库,因为它已经存在。
我的公司直到最近才意识到前端开发人员可能拥有重要的技能可以贡献,因此应用程序的整个前端都是由不了解最佳实践的开发人员编写的,而且他们往往蔑视自己的任务。 因此,代码质量参差不齐,总体上相当糟糕且不符合规范。
是的,我在 _这种_ 遗留代码库中工作:典型的 jQuery 意大利面条。
总会有人去做,而且世界上总是会有比全新项目更多的遗留代码,所以我们总是会很多。 我也不需要你的同情。 处理这些东西,学习如何应对如此大规模的前端意大利面条,让我成为一个更优秀,但也更暴躁的开发人员。
那么你如何知道你是否拥有意大利面条式 jQuery 呢? 我发现的一个可靠的代码味道是缺乏久经考验的 .toggle()
。 如果你成功地一段时间没有考虑 jQuery,它是一个可以消除跨浏览器兼容性问题的库,同时也使 DOM 查询和变异变得非常容易。 这本身并没有错,但是如果你不小心,直接的 DOM 操作可能非常难以扩展。 你编写的 DOM 操作越多,你对 DOM 变异的防御性就越强。 最终,你可能会发现自己用这种方式编写了整个代码库,并且与不太理想的范围管理相结合,你实际上是在一个所有状态都在 DOM 中的应用程序中工作,而且你永远不能相信你需要进行更改时 DOM 的状态; 更改可能会从你的应用程序中的任何地方突然出现,无论你喜欢与否。 你的代码变得更程序化,使用更多明确的指令来膨胀代码,试图从 DOM 本身获取所有需要的数据,并将其强制到需要它的状态。
这就是为什么 .toggle()
通常是第一个被放弃的:如果你不能确定某个元素是否可见,你必须使用 .show()
和 .hide()
。 我不是说 .show()
和 .hide()
应该被认为是危险的™,但我发现它们是可能存在更大问题的一个良好指标。
你可以采取哪些措施来解决这个问题? 我的同事和我发现的一种解决方案直接借鉴了我们更愿意使用的响应式框架:可观察对象和状态管理。 我们都发现,在**将 DOM 视为单向数据流**模板的同时,手动滚动状态对象和事件驱动的更新函数会导致更**可预测的结果**,这些结果更容易随着时间的推移而改变。
我们每个人解决问题的方式略有不同。 我对响应式 jQuery 的看法明显带有 Vue 注入的风格,并利用了一些“高级”CSS。
如果你查看脚本,你会发现发生了两件不同的事情。 首先,我们有一个 State
对象,它保存了页面所有值,而且我们有一堆事件。
var State = {
num: 0,
firstName: "",
lastName: "",
titleColor: "black",
updateState: function(key, value){
this[key] = value;
$("[data-text]").each(function(index, elem){
var tag = $(elem).attr("data-tag");
$(elem).text(State[tag]);
});
$("[data-color]").each(function(index, elem){
var tag = $(elem).attr("data-tag");
$(elem).attr("data-color", State[tag]);
});
}
};
我承认,我喜欢自定义 HTML 属性,并且在整个解决方案中都大量使用它们。 我一直不喜欢 HTML 类如何经常充当 CSS 挂钩和 JavaScript 挂钩,以及如果你同时将一个类用于这两个目的,你就会在脚本中引入脆弱性。 这个问题使用 HTML 属性可以完全消除。 类再次成为类,而属性则变成我需要的任何元数据或样式挂钩。
如果你查看 HTML,你会发现 DOM 中每个需要显示数据的元素都有一个 data-tag
属性,其值对应于 State
对象中包含要显示数据的属性,以及一个没有值的属性,该属性描述需要对应用它的元素进行的转换类型。 此示例有两种不同的转换类型:文本和颜色。
<h1 data-tag="titleColor" data-color>jDux is super cool!</h1>
关于事件。 我们想对数据进行的每次更改都由一个事件触发。 在脚本中,你会发现我们关心的每个事件都列出了其自己的 .on()
方法。 每个事件都会触发一个更新方法,并发送两条信息:需要更新的 State
对象中的哪个属性以及它应该设置的新值。
$("#inc").on("click", function(){
State.updateState("num", State.num + 1)
});
$("#dec").on("click", function(){
State.updateState("num", State.num - 1)
});
$("#firstNameInput").on("input", function(){
State.updateState("firstName", $(this).val() )
});
$("#lastNameInput").on("input", function(){
State.updateState("lastName", $(this).val() )
});
$('[class^=button]').on("click", function(e) {
State.updateState('titleColor', e.target.innerText);
});
这将我们带到了 State.updateState()
,即保持页面与状态对象同步的更新函数。 每次运行时,它都会更新页面上所有标记的值。 这并不是每次都重新执行页面上所有操作的最有效方法,但它要简单得多,而且正如我希望我已经清楚地说明的那样,这是一个针对不完美代码库的不完美解决方案。
$(document).ready(function(){
State.updateState();
});
更新函数首先做的就是根据它接收的属性更新值。 然后它执行我提到的两种转换。 对于文本元素,它会列出所有 data-text
节点,获取它们的 data-tag
值,并将文本设置为标记属性中的任何内容。 颜色的工作方式略有不同,它将 data-color
属性设置为标记属性的值,然后依赖于 CSS,CSS 将样式化 data-color
属性以显示正确的样式。
我还添加了一个 document.ready
,以便我们可以在加载时运行更新函数并显示默认值。 你可以从 DOM 获取默认值,也可以进行 AJAX 调用,或者像这里一样直接使用它们加载 State
对象。
就是这样! 我们所做的就是将状态保存在 JavaScript 中,观察我们的事件,并对发生的更改做出反应。 很简单,对吧?
这里有什么好处? 使用这种模式可以将单一事实来源保留在你可以控制、可以信任和可以强制执行的状态对象中。 如果你曾经失去对 DOM 正确性的信任,你只需使用不带任何参数重新运行更新函数,你的值就会再次与状态对象保持一致。
这是一种笨拙且原始的方法吗? 绝对是。 你想用它来构建整个系统吗? 当然不。 如果你有更好的工具可用,你应该使用它们。 但是如果你像我一样身处高度限制的遗留代码库中,请尝试使用响应式 jQuery 编写你的下一个功能,看看它是否会让你的代码和你的人生变得更简单。
很棒的文章,我可以立即用它。 我继承了一个意大利面条式 Shopify 网站代码库,我们只能使用 jQuery。
在使用 React 几年后,回到 jQuery 感觉就像魔法,但正如你所优雅地解释的那样,你经常会遇到状态和事实来源的问题。
在下一个重构中我一定会尝试这些方法。
我很欣赏它带来了状态的好处,但
updateState
函数会极大地增加 jQuery 搜索 DOM 的次数,相比之下,直接编写老式的忍者代码要少很多。我之所以这么说,是因为我与一些大型遗留项目合作,如果所有
document.ready
代码都被重构并在用户触发事件时每次都运行,网站就会变得异常缓慢。很棒的文章。 我很喜欢你能够将古老的意大利面条响应式化。
不,兄弟。
你需要做的是逐步淘汰 jQuery - 如果你对它创建更多依赖,你永远无法摆脱它。
“没有依赖”,好的,但你显然可以添加新代码?
因此不要添加“依赖”,只需使用足够小的引擎来拥有和复制/粘贴它。
例如,dot-dom 基本上是 512 字节的 React - 具有生命周期事件和虚拟 DOM 的有状态组件
https://github.com/wavesoft/dot-dom
它确实需要相当现代的浏览器,但使用 Babel 和一些快速更改可以轻松降级。
或者还有 Superfine,它只是一个简单的虚拟 DOM,大约 300 行代码
https://github.com/jorgebucaran/superfine
这两者都没有潜在的安全问题,但它们使你能够逐步迁移到现代 UI 模式,而且它们不必是“依赖”。
Rasmus,你没有看到的是,当我说“没有依赖”时,我是认真的。 _Node 是一个依赖,我们不允许安装它_。 这不是关于大小,而是关于自由。
@merid 如果你真的意味着“没有依赖”,那么你就不能编写任何代码,因为该网站将依赖于你编写的代码,就像你依赖于你必须绕过的已存在代码一样。 例如,如果你逐个字符地重写 React,那是依赖还是你的代码?
兄弟,如果我能做到,并且可以向后兼容 IE9,我就不会在这里工作了:)
我不理解人们对 jQuery 的愤怒。它在网页历史中的重要性不可低估,它作为跨浏览器的一种统一 API,在当时(甚至现在)很多浏览器无法遵守标准的情况下,提供了无缝的跨平台体验。可以说,它可能单枪匹马地帮助了“Web 2.0”的到来。
老实说,现代人抱怨它的体积太大,考虑到现在 JS 包越来越臃肿,这就像是在说锅嫌壶黑一样。
对于小型项目来说,使用 CDN 托管的 jQuery 并开始编写代码是完全可以的。现在唯一让这种做法几乎变得不必要的,是 Svelte。
我喜欢这篇文章的方方面面。既有创意又实用。向所有努力维护网页旧部分顺畅运行的开发者致敬!:)
在 jQuery 的最新版本中,你可以直接使用
data()
来访问数据属性。不再需要使用 getAttribute() 来获取它们了。在这个例子中,你不想这样做,而且在我看来,永远也不想这样做。.data() 只在页面加载时加载值并缓存它们。如果你在代码中修改了任何 data-* 属性,你的 JavaScript 将无法看到更新。
https://api.jqueryjs.cn/data/#data-html5
使用 data() 和 [data] 实际上是两件截然不同的事情,但之间有着微弱的联系。我理解为什么你在这个项目中无法使用 data(),但对于那些有幸完全控制代码并且没有使用其他库的人来说,使用 data() 绝对有其位置。
…正在构建一个 WebGL 原型应用,其中状态就是 DOM …没有影子 DOM 或者镜像 DOM …只是直接从 DOM 获取和设置。主要是因为我混合使用了多个库,还有 jQuery 和原生 JavaScript,而且相当随意 …有时在一个函数中同时使用它们!…很高兴知道当我需要清理所有代码时可以找到你。:)
很棒的内容。
我有点困惑为什么这会引起如此大的争议。
我曾经在 SharePoint 2010 内联网站点上做过一些事情(他们还在使用),这种模式当时会非常有用。
我唯一可能会做不同的事情,就是只在值真正发生变化时更新元素。例如:
if ($(elem).text() != State[tag]) { $(elem).text(State[tag]) }
嘿,兄弟,我非常喜欢这篇文章!我甚至受到启发扩展了你的例子,并创建了一个更通用的解决方案!:D
https://jsfiddle.net/jeyssonguevara/L7gmqsty/2/
名字“unspaguetti”只是为了好玩,应该换成更短的名称 XD
以下是一个关于这个实验可以做什么的小样本
我知道你只提供了示例,但是你可能需要缓存你在 updateState 函数中使用的选择器。我不认为每次用户触发任何事件时都搜索整个 DOM 是一个好主意。
做得好!我承认我几乎很兴奋要重构一些 jQuery 代码。
视角的转变可以改善不利的环境,真是太棒了(对所有人来说更广泛的人生教训?也许吧)。
看来这是一个相当有争议的话题。我不太理解选择这种方式的理由。听起来好像是因为你不能信任 DOM 状态… 但是你又写了从 DOM 读取数据的代码。所以,要么你可以信任它,要么你不能信任它。看起来你只是习惯了以 React 的方式做事,并且想继续那样工作。这样做本身并没有错。只要记住,接手这个项目的人将不得不处理原始的“意大利面式”代码加上你的“React 化”代码,他们可能会陷入比你更糟糕的境地。
如果遗留代码真的像你描述的那样糟糕,那么这种设置方式可能存在问题。你只在事件触发时更新 State 对象。其他代码有可能在不触发事件的情况下更改值。所以你又回到了无法信任为真理的境地。如果你在需要使用值之前运行 updateState() 函数,那你实际上也可以直接从 DOM 读取,并且不需要任何更新 State 的事件监听器。
或者,也许是我完全遗漏了什么?
我在这里演示的方法假设你的 DOM 是不可信的,但你的 State 对象是可信的。这段代码从 DOM 中提取的唯一东西是模板标签,因为你需要一些东西来挂钩,但除此之外,所有数据都存储在 State 对象中,并被推送到视图。如果你的属性或者 State 对象被远程修改,这种方法显然行不通,并且你面临的问题比这种模式所能解决的要大得多。
为什么不使用 Web Components?
它们得到了很好的支持,所以不再需要依赖,而且你的代码将井井有条。
好吧,用 React、Vue、Angular 等来替换基于 jQuery 的应用程序是可以的,我完全赞成。但是!不要在现有的 jQuery 项目上这样做。大多数情况下,不仅仅是 jQuery,还有很多 jQuery 插件在使用。比如 DataTables.js 用于实现表格查找、过滤和编辑,还有下拉选择插件、日期选择器插件、图表插件、DOM 操作插件等等。这增加了将现有 jQuery 项目进行转换的工作量。更不用说来自后端的数据,这也是另一个“问题”,而且根据后端使用的技术,转换会变得容易或困难。在你意识到之前,你已经花费了 10 个月的时间在一个本该“轻松”的转换项目上,而最终却成了一个比预计成本高得多的转换项目。
实际上,维护一个基于 jQuery 的现有项目并没有什么可耻的。如果你需要“转换”现有的 jQuery 项目,最简单的方法是从头开始一个“MK2”项目,并使用你想要的目标框架。
6 年后,会出现一个更好的新框架,叫做“FizleJuice”,然后你应该用它替换“现在的框架”,然后… 嘿,似曾相识!
;)
您好:
非常棒… 谢谢您分享这个内容,这对开发者来说真的很有帮助。