我们最近完成了一个里程碑,将jQuery完全从GitHub.com的前端代码中移除。这标志着我们数年来逐步移除jQuery这个渐进式的过程终于结束了,我们现在已经完全移除了这个库。这篇文章将介绍我们是如何依赖上jQuery的,以及后来我们意识到不再需要它,到最后我们并没有使用另一个库或框架取代它,而是使用标准的浏览器API实现了我们所需要的一切。
为什么在早期jQuery对我们来说是有意义的GitHub.com在2007年底开始使用jQuery 1.2.1。那是谷歌发布Chrome浏览器的前一年。当时还没有通过CSS选择器来查询DOM元素的标准方法,也没有动态渲染元素的样式的标准方法,而Internet Explorer的XMLHttpRequest接口与其他很多API一样,在浏览器之间存在不一致性问题。
jQuery让DOM操作、创建动画和“AJAX”请求变得相当简单——基本上,它让Web开发人员能够创建更加现代化的动态Web体验。最重要的是,使用jQuery为一个浏览器开发的代码也适用于其他浏览器。在GitHub的早期阶段,jQuery让小型的开发团队能够快速进行原型设计并开发出新功能,而无需专门针对每个Web浏览器调整代码。
基于jQuery简单的接口所构建的扩展库也成为GitHub.com前端的基础构建块:pjax(https://github.com/defunkt/jquery-pjax)和facebox(https://github.com/defunkt/facebox)。
我们将永远不会忘记John Resig和jQuery贡献者创建和维护的这样一个有用的基本库。
后来的Web标准多年来,GitHub成长为一家拥有数百名工程师的公司,并逐渐成立了一个专门的团队,负责JavaScript代码的规模和质量。我们一直在排除技术债务,有时技术债务会随着依赖项的增多而增长,这些依赖项在一开始会为我们带来一定的价值,但这些价值也随着时间的推移而下降。
我们可以将jQuery与现代浏览器支持的Web标准的快速演化进行比较:
$(selector)模式可以使用querySelectorAll()来替换;
现在可以使用Element.classList来实现CSS类名切换;
CSS现在支持在样式表中而不是在JavaScript中定义可视动画;
现在可以使用Fetch Standard执行$.ajax请求;
addEventListener()接口已经足够稳定,可以跨平台使用;
我们可以使用轻量级的库来封装事件委托模式;
随着JavaScript语言的发展,jQuery提供的一些语法糖已经变得多余。
另外,链式语法不能满足我们想要的编写代码的方式。例如:
$('.js-widget') .addClass('is-loading') .show()这种语法写起来很简单,但是根据我们的标准,它并不能很好地传达我们的意图。作者是否期望在当前页面上有一个或多个js-widget元素?另外,如果我们更新页面标记并意外遗漏了js-widget类名,浏览器是否会抛出异常会告诉我们出了什么问题?默认情况下,当没有任何内容与选择器匹配时,jQuery会跳过整个表达式,但对我们来说,这是一个bug。
最后,我们开始使用Flow来注解类型,以便在构建时执行静态类型检查,并且我们发现,链式语法不适合做静态分析,因为几乎所有jQuery方法返回的结果都是相同的类型。我们当时之所以选择Flow,是因为@flow weak模式等功能可以让我们逐步将类型应用于无类型的代码库上。
总而言之,移除jQuery意味着我们可以更多地依赖Web标准,让MDN Web文档成为前端开发人员事实上的默认文档,在将来可以维护更具弹性的代码,并且可以将30KB的依赖从我们的捆绑包中移除,加快页面的加载速度和JavaScript的执行速度。
逐步解耦虽然定下了最终目标,但我们也知道,分配所有资源一次性移除jQuery是不可行的。这种匆匆忙忙的做法可能会导致网站功能出现回归。相反,我们采取了以下的策略:
// 旧方法 $(document).on('ajaxSuccess', 'form.js-widget', function(event, xhr, settings, data) { // 将响应数据插入到DOM中 })我们选择触发假的ajax*生命周期事件,并保持这些表单像以前一样异步提交内容,而不是立即重写所有调用,只是会在内部使用fetch()。
我们自己维护了jQuery的一个版本,每当发现我们不再需要jQuery的某个模块的时候,就会将它从自定义版本中删除,并发布更轻量的版本。例如,在移除了jQuery的CSS伪选择器之后(如:visible或:checkbox)我们就可以移除Sizzle模块了,当所有的$.ajax调用都被fetch()替换时,就可以移除AJAX模块。这样做有两个目的:加快JavaScript执行速度,同时确保不会有新代码试图使用已移除的功能。