从jQuery到原生JS:DOM操作、事件系统与Ajax能力全面对比

发布时间:2026/10/21 7:45:48
从jQuery到原生JS:DOM操作、事件系统与Ajax能力全面对比 接手一个维护了三年的后台管理系统第一眼扫过去满屏都是$()调用我当时脑子里冒出来的想法和很多人一样这都什么年代了还在用 jQuery但等到真正沉下心去读代码、改需求、调 bug 的时候我发现自己之前对 jQuery 的轻视有点草率。JS vs jQuery 这个老生常谈的对比放到今天依然值得认真聊清楚——不是为了给过气技术唱挽歌而是想弄明白一件事原生 JavaScript 这些年到底补齐了哪些能力jQuery 当年那些让一代开发者上瘾的设计跟现代原生 API 之间还有多大差距。无论你是在维护老项目还是打算把存量代码迁移到原生 JS这篇整理应该都能帮上忙。1. 一个老项目让我重新审视 jQuery 的生存逻辑1.1 满屏$()背后的真实感受那个后台系统大概是三年前搭的技术栈是 jQuery 一套自研的模板渲染。打开任何个业务页面几乎都能看到这样的代码$(document).ready(function () { $(#data-list).on(click, .edit-btn, function () { var id $(this).data(id); $(#edit-modal).modal(show).find(form).attr(action, /update/ id); }); });说实话接手第一周我的内心是拒绝的。但你会发现一个反直觉的事实这个系统跑得很稳。页面交互没问题数据请求没问题CDN 上那个压缩过的 jQuery 文件也无怨无悔地服务了三年。稳定背后是有原因的——jQuery 在 DOM 操作和事件处理上做了一层非常厚实的封装只要开发者按它的套路来代码基本不会因为浏览器差异而翻车。这让我意识到在嘲笑老技术之前得先理解它当初为什么能活下来。1.2 当年的 jQuery 到底在解决什么问题用现在的眼光看jQuery 的 API 设计似乎平平无奇但如果把一个开发者扔回 2006 年他会感激涕零。那时候浏览器生态可以说是各自为政DOM API 数量少、标准混乱。想绑定事件在老版本浏览器里可能得写attachEvent其他浏览器里又得写addEventListener想取一批元素getElementsByClassName也不是到处都有想发一个异步请求XMLHttpRequest在各个浏览器身上的行为还不完全一致。jQuery 做的最关键的一件事是用特性检测代替浏览器检测。它不关心你用的是哪个浏览器只关心这个浏览器支不支持某个能力然后把统一的接口暴露给开发者。于是你写一句$(#id).on(click, fn)它在老浏览器里也能正常绑上事件。这种一次编写处处运行的价值在跨端、跨版本的时代是致命的吸引力。另一个被忽视的点是jQuery 直接借用了 CSS 选择器语法来操作 DOM。前端开发者本来就懂 CSS$(.nav .item)这种写法几乎零学习成本比原生那套getElementById 遍历 childNodes 的写法直观太多了。一种语言的成功往往是因为它让某个高频操作变得极其舒服jQuery 做到了。1.3 今天聊这个对比还有什么价值可能有人会说现在都写现代框架或者原生 JS 了jQuery 已经退出历史舞台讨论它没有意义。但我认为恰恰相反这个对比在今天有三个实际用途第一大量存量项目还在用 jQuery。你随便去找一家传统企业的后台系统八成能翻到 jQuery 的痕迹。这些系统没那么容易推倒重写维护它们、给它们接新需求就必须懂 jQuery 的写法也得知道怎么用原生 JS 和它协作。第二jQuery 的设计思想在现代前端里并没有消失。你仔细想想隐式迭代、批量操作、事件委托优化、链式调用——这些概念在今天的现代框架和原生 API 里依然存在只是换了个马甲。理解了 jQuery 为什么这样设计再去看现代这些工具会顺畅很多。第三面试和团队协作里这个问题依然高频。当候选人能把jQuery 和原生 JS 的核心差异讲清楚说明他不只是会用 API而是真正理解了 DOM 操作模型的演进。2. 选择器与 DOM 集合操作从 API 设计看代差2.1$()与querySelectorAll()返回对象的本质差异表面上看$(.item)和document.querySelectorAll(.item)都是在找元素但两者返回的对象有本质区别。querySelectorAll返回的是一个NodeList它本质上是静态元素的集合虽然有forEach可以遍历但它不是数组也没有一堆现成的 DOM 操作方法挂在身上。jQuery 返回的是一个jQuery对象——一个类数组结构里面封装了几十个操作 DOM 的方法。这个差异直接决定了后续代码的写法// 原生 JS const items document.querySelectorAll(.item); items.forEach(function (item) { item.classList.add(active); }); // jQuery $(.item).addClass(active);你会发现原生 JS 的写法是先把集合拿出来然后手动遍历再逐个操作元素jQuery 的写法是声明我要给哪些元素加类剩下的交给库。后者少了一整层的循环思维负担。2.2 隐式迭代才是真正的生产力优势jQuery 最核心的体验就是隐式迭代。什么叫隐式迭代就是你调用$(.btn).css(color, red)时jQuery 会在内部自动帮你把这个操作应用到集合里的每一个元素上。你不需要写循环库替你循环。这带来的一个显著好处是代码的意图表达更贴近自然语言。你关注的是做什么而不是怎么遍历。给所有.btn设置红色这句话翻译成代码就是$(.btn).css(color, red)几乎是逐字对照。原生的写法则要求你先把遍历动作写出来意图被过程打断了一截。但隐式迭代也有它的反面。如果你对集合做了多次连续操作jQuery 每次都会重新遍历整批元素不会因为你的链式调用而合并成只遍历一次// 看起来优雅但内部遍历了三次 $(.item).addClass(active).css(color, red).attr(data-state, on);这段代码在语义上没问题性能上也未必是瓶颈但理解这一点很重要链式调用不等于批量优化它只是语法上的连续调用。在这个维度上原生写法有时反而更显式——你知道自己在循环里做三件事只需要一次遍历。2.3 选择器引擎的扩展能力Sizzle 走了很前面jQuery 自带一个选择器引擎叫 Sizzle它的作用不只是解析 CSS 选择器还扩展了一批 CSS 标准里没有的语法。比如:first、:last、:eq(2)、:visible、:hidden这种基于索引或状态的筛选。相比之下querySelectorAll只能识别标准的 CSS 选择器。你想在原生 JS 里实现取第二个.item要么写document.querySelectorAll(.item)[2]要么用:nth-child()这类标准伪类。想实现找出页面上所有可见元素原生 API 就没有这个能力你必须自己写一个getComputedStyle加过滤的筛选函数。下面这张表能直观地看出边界能力querySelectorAlljQuery 选择器标准 CSS 选择器支持支持:first/:last/:eq()索引伪类不支持支持:visible/:hidden状态筛选不支持支持内部缓存与二次过滤不支持支持.filter()自定义选择器不支持需手动filter原生支持这也是我在迁移老项目时经常踩到的坑很多 jQuery 代码里用了$(li:visible)这种筛选逻辑原生 JS 里没有直接等价物必须改成先取所有 li再用getComputedStyle判断display是否可见。功能能实现但代码量一下子多了不少。2.4 链式调用语法糖还是设计哲学jQuery 的链式调用是它的招牌特性实现原理其实特别简单大多数方法在操作完成后返回this。所以你可以一个接一个地调用方法写出一段读起来像流水账一样的代码$(#card) .addClass(highlight) .fadeIn() .find(.title) .text(更新后的标题);这种写法的好处是代码简短、上下文一致也容易读。坏处是调试困难如果中间某一步操作出了错你很难在链式调用里定位到底是哪一步出了问题控制台报错往往指着整条链的最后一句话。我在实际项目中见过好几条十几层的链式调用读起来像天书改起来像拆炸弹。我的建议是超过三个方法的链就考虑拆成变量缓存。比如把$(.list)缓存成const $list $(.list);每一步操作单独写成一行。这对维护期的项目尤其重要——你永远不知道三个月后接手代码的那个人能不能承受一串 200 字符的方法链。3. 事件系统on()与addEventListener()的深层差异3.1 表面相同内在不同表面上看jQuery 的.on(click, fn)和原生addEventListener(click, fn)都是绑定事件用法差异不大。但 jQuery 的背后做了很多隐藏工作。第一jQuery 在内部对事件对象做了标准化。老版本浏览器里event对象有各种差异——target还是srcElement、stopPropagation存在与否、preventDefault是否可靠。用 jQuery 绑事件你统一用event.target、event.stopPropagation()就行库里已经替你处理好了。原生addEventListener在现代浏览器里同样标准但如果你要兼容非常老的浏览器这套统一封装的价值就体现出来了。第二jQuery 的on()支持直接在参数里传入选择器做事件委托原生addEventListener需要自己判断事件目标。这个差异在动态 DOM 场景下会非常明显。3.2 事件命名空间一个被低估的能力jQuery 的on()支持事件命名空间这是原生 API 始终没有提供的功能// 给同一个事件绑两套逻辑 $(#btn).on(click.menu, openMenu); $(#btn).on(click.tab, switchTab); // 只解绑菜单相关的逻辑保留选项卡逻辑 $(#btn).off(click.menu);原生removeEventListener只能通过传入绑定时保存的函数引用去精确移除而且匿名函数一旦绑上去基本就黏死了——你想移除它手里却没有引用。更麻烦的是如果你想把某个元素上的所有 click 监听都清掉再重新绑一批原生只能寄存一个 handler 数组挨个移除非常笨重。实际开发里这个差异最常在弹窗关闭时清理事件的场景触发。jQuery 里一句$(#modal).off(click.modal)就能安全清理原生 JS 里你可能得给每个函数单独命名、登记真的一点都不省心。3.3 事件委托一行 vs 一打事件委托是 jQuery 用得最爽的场景之一。假设有一个动态生成列表列表项点击时需要触发逻辑。jQuery 的写法十分干净// jQuery事件绑在父容器上通过选择器过滤子元素 $(#list).on(click, .item, function () { console.log(clicked, this); });原生的等价实现要麻烦不少document.getElementById(list).addEventListener(click, function (e) { const el e.target.closest(.item); if (el this.contains(el)) { console.log(clicked, el); } });用closest()找最近的.item再用contains()确认它确实在容器内——这套逻辑在 jQuery 内部被封装好了你还不需要处理e.target是文本节点或者子元素之类的边界情况。这也是为什么很多从 jQuery 转原生的人会觉得代码变长了一倍。3.4 this 指向的坑位迁移jQuery 绑定的回调里this指向触发事件的 DOM 元素$(.btn).on(click, function () { $(this).text(已点击); // this 是按钮 DOM 元素 });原生addEventListener的回调里this也指向当前元素这一点是一致的。但在你实际写代码的时候很可能因为用了箭头函数而踩坑——箭头函数没有自己的this它会向上穿透到外层作用域// 踩坑写法 element.addEventListener(click, () { // 此处的 this 不指向 element this.classList.add(active); // 一旦这里出错很难想起来是 this 的问题 });我在同事的代码里见到过几次箭头函数 in event listener引发的诡异 bug。排查方法其实很简单把箭头函数换回普通function先验证this指向再继续排查其他业务逻辑。4. Ajax 与异步流程从回调到 Promise 的演进4.1$.ajax()一行代码的背后jQuery 的 Ajax 封装在一个老开发者眼里简直是良心之作。它帮你处理了太多原生XMLHttpRequest的琐碎细节// jQuery 时代 $.ajax({ url: /api/users, method: GET, dataType: json, success: function (data) { console.log(data); }, error: function (xhr, status, error) { console.error(error); }, });这里面的dataType: json非常关键——jQuery 会自动把响应体解析成 JavaScript 对象你不用自己JSON.parse还要处理异常。原生XMLHttpRequest拿到的是一个字符串你还得手动解析、手动处理 HTTP 错误状态代码量是翻倍的。另外$.getJSON、$.post这些语法糖进一步压缩了常见场景的代码量。对于当年那些不熟悉异步细节的开发者jQuery 把发请求拿数据这件事变成了三个参数的事这个简化的价值怎么强调都不过分。4.2fetch来了之后差异并没有消失用fetch写原生异步请求很多新手会天真地以为只是换了个函数名。但fetch和$.ajax之间有几个关键差异能力$.ajaxfetchJSON 自动解析有没有需要手动response.json()HTTP 错误状态处理自动进入error回调不 reject需要手动判断response.ok超时控制有timeout参数没有要配合 AbortController携带 Cookie同域请求默认带上默认不带需要credentials: include中断请求有xhr.abort()需要 AbortController 手动实现最坑的一点是fetch对 HTTP 404/500 的处理方式。fetch在这些状态码下依然会 resolve而不是 reject。如果你不加response.ok检查404 响应会被当成成功处理。// 原生 fetch 的正确姿势 fetch(/api/data) .then((response) { if (!response.ok) { throw new Error(HTTP response.status); } return response.json(); }) .catch((err) console.error(err));这些差异意味着把 jQuery 的 Ajax 改成 fetch不是换个函数名而是要对整个状态处理逻辑重写。老项目里几十处$.ajax调用每一处的成功/失败分支可能都依赖 jQuery 的处理语义批量迁移的风险是不低的。4.3 改造老请求的真实经验我自己的做法是中间层替换法在源码级别保留一个全局的request函数签名与$.ajax保持兼容内部用fetch实现再把业务代码里的$.ajax替换成request。这样做的收益是业务代码几乎不动所有差异被收敛到这一个函数里。// 替代函数一例 function request(options) { const { url, method GET, data, dataType json } options; return fetch(url, { method, body: data ? JSON.stringify(data) : undefined, }).then((res) { if (!res.ok) throw new Error(HTTP res.status); return dataType json ? res.json() : res.text(); }); }当然这只是骨架真实项目还得加超时、错误码归一、loading 状态等。但这种接口兼容的改造方式比逐行重写几十处$.ajax稳妥得多。我先挑一个低频接口做验证跑通一轮再批量铺开整个迁移周期控制在两个迭代内回归压力小很多。5. 性能实测哪些差距真实存在哪些只是谈资5.1 选择器性能的直觉误区很多人一谈起jQuery 为什么慢张口就是选择器性能差。这个说法得分场景。现代浏览器的querySelectorAll本身是 C 实现的选择引擎jQuery 3.x 在支持querySelectorAll的环境下也会转调原生实现——换句话说标准选择器的性能差距小到可以忽略。真正慢的场景是:visible、:eq()这类自定义扩展伪类它们需要 jQuery 自己解析并逐一过滤没法用原生的选择引擎加速。我曾经在一个包含近万个 DOM 节点的测试页面上跑了简单测试$(#id)和document.getElementById()对比原生确实快但对用户来说那几毫秒的差异毫无感知。真实的性能瓶颈永远不在选择器本身快 5 毫秒而在DOM 操作触发了多少次重排重绘。5.2 真正的性能陷阱重复查找与重排重绘真正值得警惕的是这样一类代码——在循环里反复调用选择器而不做缓存// 反面教材 for (let i 0; i 100; i) { $(.item- i).css(left, i * 10 px); }每次$()都是一次新的查找加上每次.css()都可能触发一次样式重计算。这里的问题不在于 jQuery而在于没有缓存 反复操作 DOM这个习惯。原生写法同样会踩这个坑你得手动把querySelectorAll的结果缓存到一个变量里。jQuery 的链式操作还有一层隐藏成本如果每个环节都往 DOM 上写一次重排次数会累积。真正的优化办法不是改用原生 API而是合并操作——使用 class 一次切换或者离线处理完再一次性把 HTML 片段塞入容器。你从 jQuery 换到原生时这个优化逻辑一点都不会变。5.3 体积、加载与初始化开销jQuery 3.7 的压缩版 gzip 后在 30KB 左右。放在今天的项目里这个体积谈不上重但问题在于你只用到它十分之一的功能。如果项目里只用选择器和事件绑定为了这些用掉 30KB 的网络传输和解析成本移动端场景里就有点不划算。但反过来说在老项目里因为这 30KB 就大动干戈搞迁移性价比更低。很多后台管理系统的真实瓶颈在数据查询、图片加载、渲染结构上jQuery 那 30KB 根本不排不上号。我在优化某页面时用Performance面板看了加载瀑布图jQuery 的加载时间只占整条瀑布的很小一块节省它的收益远不如压缩一张大图来得明显。性能优化不 chat engineer 至上得先看数据说话。6. 从 jQuery 迁移到原生 JS 的渐进路线6.1 高频方法替代清单如果你决定动手迁移下面这份高频方法对照表可以先收藏起来平时写代码时对照着看功能jQuery 写法原生 JS 写法选择单个元素$(#id)document.getElementById(id)选择多个元素$(.item)document.querySelectorAll(.item)遍历集合.each(function(i, el){})forEach((el, i) {})添加 class.addClass(x)element.classList.add(x)删除 class.removeClass(x)element.classList.remove(x)绑定事件.on(click, fn)element.addEventListener(click, fn)事件委托.on(click, .item, fn)手动closest()判断设置属性.attr(disabled, true)element.setAttribute(disabled, )或直接.disabled true修改文本.text(新内容)element.textContent 新内容隐藏元素.hide()element.style.display none发起请求$.get(url)fetch(url).then(r r.json())这份清单看起来很简单但真正迁移时你会碰到很多细枝末节的语义差异。比如 jQuery 的.hide()设置的是display: none但原来的display值如果是flexshow 的时候要恢复成flex而不是简单的block.text()和.textContent基本一致但 jQuery 的.text()在读取时会把所有空格的细节处理掉。这些都是文档里不会提醒你的隐形坑。6.2 分模块迁移的节奏把握我不建议一次性重写全部页面风险太大。我的建议是按成本从低到高的顺序推进先替换$(document).ready。这个最简单就是DOMContentLoaded事件纯生命周期替换改完不会破坏交互。再替换静态页面的 DOM 读写。只涉及简单的选择器、class 切换、文本修改用querySelectorclassList就能搞定回归测试也容易。接着替换事件委托逻辑。这一步要小心this指向和closest()边界但逻辑本身比较独立。最后才动 Ajax。因为涉及请求状态处理、JSON 解析、错误分支改动面最大要留足够的时间专门测试。每完成一类我都在浏览器里手动回归一轮关键路径而不是全部改完再统一测试。这看起来慢实际反而省时间——定位问题时你不用同时怀疑是选择器坏了还是事件坏了还是请求坏了。6.3 迁移前先想明白三件事在开工之前有四个问题必须想清楚第一存量用户是否还包含老版本浏览器如果项目要兼容老旧浏览器环境迁移到原生 JS 的成本会急剧上升因为很多现代 API 需要 polyfill 才能工作而 jQuery 自己就已经把兼容层处理好了。这种情况下保留 jQuery 反而是更理性的选择。第二团队的熟悉度。如果团队里基本都是写 jQuery 的老手突然全部改原生 JS开发效率会短期下降。我一般建议新代码用原生老代码逐步改给团队一个缓冲期。第三项目的预期寿命。如果这个项目半年后就要被新系统替换那迁移就是纯浪费成本如果它还要活五年以上那早点动手债务越早还越轻松。第四兼容方案是否可控。对于个别旧浏览器用少量 polyfill 是否能覆盖如果只有一小撮特例完全可以接受 polyfill 的成本让迁移继续推进。这些问题想清楚再动手比急着炫技用原生 API 去替换一切要重要得多。最后再分享一点个人的体会。那次维护老项目的经历让我最终没有把 jQuery 从代码库里彻底删除。它还在那里作为全局工具函数被老页面继续使用而新页面我已经在用原生 JS 配合现代模块写法落地。不是我不愿意改而是意识到迁移本身应该有目的——如果它没有带来可量化的收益只是满足了一种要用新技术的冲动那这个动作就是纯成本。真正有价值的是借由JS vs jQuery这组对比把浏览器原生能力完整地摸了一遍。当你理解了 jQuery 当初帮你封装了什么、原生 API 现在又补齐了什么再去看任何新框架、新工具都会有一种这层封装我认识的踏实感。写代码这件事知道某个 API 怎么用只是底线知道为什么会有这个 API才算真的入了门。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询