【JS DOM】告别 jQuery:原生 JS DOM 操作神技(closest, matches, classList)
【JS DOM】告别 jQuery:原生 JS DOM 操作神技(closest, matches, classList)
所属专栏: 《前端小技巧集合:让你的代码更优雅高效》
上一篇: 【CSS 优化】一行代码提升用户体验:你必须知道的几个 CSS 小属性
作者: 码力无边
✨ 引言:那一年,我们都曾是 “$” 的信徒
嘿,各位前端道友,我是码力无边。欢迎回到我们的修炼道场!
在过去的几篇文章里,我们一直在 CSS 的奇妙世界里遨游,用各种神兵利器打磨页面的“血肉”与“筋骨”。但一个真正有生命的网页,离不开 JavaScript 赋予它的“灵魂”——交互。而交互的核心,就是对 DOM (文档对象模型) 的操作。
请允许我带大家进行一次“考古”。在遥远的“上古时代”(大概是 10 年前),当我们要对 DOM 做点什么时,我们的第一反应是什么?
// 引入一个神奇的库
<script src="jquery.min.js"></script>
// 然后,我们开始念动咒语...
$('#myButton').on('click', function() {
$(this).addClass('active').siblings().removeClass('active');
var parentCard = $(this).closest('.card');
parentCard.find('.card-title').text('我被点击了!');
if (parentCard.is(':visible')) {
// ... do something
}
});
$,这个简洁而强大的符号,曾是无数前端开发者的信仰。jQuery 以其无与伦比的链式调用、简洁的 API 和强大的浏览器兼容性,统治了一个时代。它让我们这些“切图仔”也能轻松地写出复杂的交互逻辑。
然而,时代变了。IE 这座大山已经(基本)被移走,现代浏览器纷纷遵循 W3C 标准,原生 JavaScript 的能力得到了前所未有的增强。过去那些需要 jQuery 才能轻松实现的操作,现在用原生 JS 也能优雅地完成,而且无需加载任何外部库,性能更高,代码也更符合现代前端的开发范式。
今天,码力无边就要带大家“破除迷信”,学习几招原生 JS 的 DOM 操作“真传”,它们分别是:
element.closest():向上查找的“导航仪”。element.matches():判断元素的“验身神器”。element.classList:管理样式的“贴身管家”。
掌握它们,你就能在很多场景下自信地对 jQuery 说一声:“谢谢你,再见!”,迈入更轻量、更现代的原生 JS 开发新纪元。
一、element.closest(selector) —— 寻找祖先的“GPS”
这是我个人认为最能直接替代 jQuery 核心功能之一的 API。
1.1 jQuery 的经典场景:事件委托
事件委托是前端性能优化的一个重要技巧。我们不给每个子元素都绑定事件,而是把事件监听器绑定在它们的父元素上,然后通过判断事件源(event.target)来执行相应的逻辑。
在 jQuery 中,我们通常这样做:
$('.card-list').on('click', '.card-button', function(event) {
// 这里的 `this` 已经是被匹配的 '.card-button' 元素
var card = $(this).closest('.card');
// ... 对 card 进行操作
});
这个 .closest('.card') 非常方便,它能从当前点击的按钮开始,向上查找第一个匹配 .card 选择器的祖先元素。
1.2 原生 JS 的优雅替代
在原生 JS 中,我们如何实现同样的效果?答案就是 element.closest()!
<div class="card-list">
<div class="card">
<p>Card 1</p>
<button class="card-button">Click Me</button>
</div>
<div class="card">
<p>Card 2</p>
<button class="card-button">Click Me</button>
</div>
<div class="card">
<p>Card 3 <span>(I'm inside a span) <button class="card-button">Click Me</button></span></p>
</div>
</div>
const cardList = document.querySelector('.card-list');
cardList.addEventListener('click', (event) => {
// event.target 是用户实际点击的那个元素,可能是 button,也可能是 button 里的 span
// 使用 closest() 来找到我们真正关心的那个按钮
const button = event.target.closest('.card-button');
// 如果点击的不是按钮或其子元素,button 会是 null
if (!button) {
return;
}
// 找到了按钮,现在再用 closest() 去找它所在的卡片
const card = button.closest('.card');
if (card) {
card.style.backgroundColor = 'lightblue';
console.log(`你点击了 ${card.querySelector('p').innerText} 里的按钮`);
}
});
closest() 的神奇之处:
- 从自身开始查找:它会先检查当前元素(
event.target)是否匹配选择器,如果匹配,就直接返回自身。 - 一路向上:如果不匹配,它会像爬楼梯一样,一层一层地检查父元素、祖父元素…直到找到第一个匹配的祖先,或者到达文档的根节点(
<html>)都没找到,此时它会返回null。
这种行为完美地解决了事件委托中的一个核心痛点:用户可能点击的是目标元素内部的子元素(比如按钮上的图标或文字 <span>)。通过 event.target.closest('.card-button'),我们总能准确地定位到我们想要操作的那个可交互的父级元素,而不用写一堆 if (event.target.nodeName === 'BUTTON' || event.target.parentElement.nodeName === 'BUTTON') 这样的丑陋代码。
二、element.matches(selector) —— “你是不是我要找的人?”
有时候,我们不想查找祖先,我们只想知道当前这个元素本身是否匹配某个 CSS 选择器。
2.1 jQuery 的 .is()
在 jQuery 中,.is() 方法非常方便:
if ($(this).is('.active')) {
// ...
}
if ($(event.target).is('input[type="checkbox"]')) {
// ...
}
2.2 原生 JS 的 matches()
原生 JS 提供了几乎完全一样的功能,它就是 element.matches()。它接受一个 CSS 选择器作为参数,如果当前元素匹配该选择器,就返回 true,否则返回 false。
让我们重写上面的事件委托例子,用一种不同的逻辑:
cardList.addEventListener('click', (event) => {
let target = event.target;
// 如果当前点击的元素不是 .card,就一直向上找,直到找到 .card 或者到顶
while (target && target !== cardList) {
// 关键:判断当前遍历到的元素是不是我们要找的按钮
if (target.matches('.card-button')) {
const card = target.closest('.card');
card.style.backgroundColor = 'lightgreen';
console.log('通过 matches 找到了按钮!');
// 找到并处理后,可以停止循环
break;
}
target = target.parentElement;
}
});
虽然在这个特定场景下,closest() 更简洁,但 matches() 在其他地方大放异彩。比如,在一个复杂的表单里,你想根据点击的元素类型做不同的事:
form.addEventListener('click', (event) => {
const target = event.target;
if (target.matches('input[type="submit"]')) {
console.log('提交按钮被点击了!');
} else if (target.matches('.form-help-icon')) {
console.log('帮助图标被点击了!');
showHelpTooltip(target);
} else if (target.matches('a.external-link')) {
if (!confirm('你将要离开本站,确定吗?')) {
event.preventDefault();
}
}
});
matches() 让你的 if/else 逻辑变得像写 CSS 选择器一样清晰和强大。
三、element.classList —— 优雅的 class 管理器
在前端开发中,我们 90% 的动态样式变化,都是通过添加、删除或切换 CSS 类名来实现的。
3.1 jQuery 的 class 操作
jQuery 的 addClass(), removeClass(), toggleClass(), hasClass() 简单好用,深入人心。
var $el = $('#myElement');
$el.addClass('active highlight');
$el.removeClass('old-class');
$el.toggleClass('is-visible');
if ($el.hasClass('some-class')) {
// ...
}
3.2 原生 JS 的 classList 对象
原生 JS 为每个元素提供了一个 classList 属性。它是一个 DOMTokenList 对象,提供了比直接操作 className 字符串安全、方便得多的方法。
classList 就像一个 class 的“贴身管家”,它有以下常用方法:
add(...classNames): 添加一个或多个类名。remove(...classNames): 删除一个或多个类名。toggle(className, [force]): 切换一个类名。如果存在就删除,不存在就添加。如果提供了第二个参数force(布尔值),true表示强制添加,false表示强制删除。contains(className): 检查是否存在某个类名,返回true或false。replace(oldClass, newClass): 替换一个已有的类。
让我们用 classList 来实现一个经典的“手风琴”效果:
<div class="accordion">
<div class="accordion-item">
<div class="accordion-header">第一项</div>
<div class="accordion-content">内容一...</div>
</div>
<div class="accordion-item active">
<div class="accordion-header">第二项</div>
<div class="accordion-content" style="display:block;">内容二...</div>
</div>
</div>
const accordion = document.querySelector('.accordion');
accordion.addEventListener('click', (event) => {
const header = event.target.closest('.accordion-header');
if (!header) return;
const currentItem = header.parentElement;
// 切换当前点击项的 active 类
currentItem.classList.toggle('active');
// (可选) 实现“只打开一个”的逻辑
const allItems = accordion.querySelectorAll('.accordion-item');
allItems.forEach(item => {
// 如果不是当前点击的项,就强制移除 active 类
if (item !== currentItem) {
item.classList.remove('active');
}
});
});
在这个例子里,classList.toggle() 和 classList.remove() 让我们能够精确、安全地控制类名,而不用去拼接或切割 className 字符串,代码既清晰又健壮。
写在最后:拥抱原生,回归本质
jQuery 是一个伟大的先行者,它在那个混沌的年代为我们披荆斩棘,统一了江湖。我们应该感谢它,但不必再迷恋它。
随着浏览器标准的统一和原生 API 的日益强大,closest, matches, classList 只是冰山一角。还有 querySelector/querySelectorAll, forEach (NodeList), fetch API, Promises, async/await… 整个原生 JavaScript 生态已经变得异常强大和便捷。
转向原生 JS,不仅仅是为了那一点点性能提升,或减少一个 HTTP 请求。更重要的是,它让我们回归了 Web 开发的本质,让我们更深入地理解浏览器是如何工作的。它让我们写的代码更符合平台标准,更经得起时间的考验。
所以,道友们,从现在开始,在你下一个项目中,当你想用 $ 的时候,请先停下来想一想:这个功能,原生 JS 是不是已经有了更优雅的实现?
相信我,当你敲下 element.closest() 并看到它完美工作时,那种“原来我也行”的喜悦,会让你对前端开发有更深的理解和热爱。
专栏预告与互动:
DOM 操作的效率提升了,但 JS 的代码逻辑本身如何写得更“骚”?数组是我们在 JS 中处理最多的数据结构,你真的精通它的所有玩法吗?
下一篇,我们将深入 JS 数组的“瑞士军刀”——
Array.reduce(),探索它除了求和之外,还有哪些能让你同事惊呼“卧槽”的神仙用法,比如数据转换、数组去重、甚至实现pipe函数!觉得码力无边的“真传”对你有用?别忘了点赞、收藏、关注,为我注入下一篇的创作真气!
今日话题: 在你过去的开发经历中,有没有哪个 jQuery 的功能是你觉得最“难以割舍”的?分享出来,我们一起看看原生 JS 有没有完美的替代方案!
更多推荐



所有评论(0)