深入对比原生JavaScript与jQuery在获取兄弟元素时的API差异与底层实现,涵盖相邻节点、全部兄弟、区间遍历及选择器过滤的完整代码示例与避坑指南。
原生JS与jQuery获取兄弟元素的代码写法对比
时间:2026-11-05
浏览:216
文章目录
.siblings()、.next()、.prev()、.nextAll()、.prevAll()),返回类数组的 jQuery 对象且容忍空查询;而在原生现代 JavaScript 中,相邻元素已具备高效的只读属性 nextElementSibling 与 previousElementSibling,全量兄弟与区间兄弟则依托父级子元素列表 parentElement.children 配合标准数组方法(filter)或 while 循环实现精准抓取。
一、获取相邻兄弟元素:next() 与 prev() 的原生替代
在界面交互中最常见的场景是获取当前元素的“下一个元素”或“上一个元素”。例如在手风琴折叠菜单中点击标题展开紧随其后的内容容器,或在分步表单中高亮前一个步骤节点。
1. 获取下一个相邻元素
在 jQuery 中,通过 .next() 方法即可捕获紧随其后的同级元素:
// jQuery 写法
const $next = $('#current-item').next();
if ($next.length > 0) {
$next.addClass('highlight');
}
而在原生 JavaScript 中,对应的现代标准属性是 nextElementSibling:
// 原生 JS 写法
const el = document.getElementById('current-item');
const nextEl = el.nextElementSibling;
if (nextEl) {
nextEl.classList.add('highlight');
}
2. 获取上一个相邻元素
获取前驱相邻元素的对应关系如下:
// jQuery 写法
const $prev = $('#current-item').prev();
// 原生 JS 现代写法
const el = document.getElementById('current-item');
const prevEl = el.previousElementSibling;
3. 核心避坑:为什么必须用 ElementSibling 而非 Sibling?
初学者在编写原生 JavaScript 时,最常犯的错误是误调用 el.nextSibling 或 el.previousSibling。这两个早期的 DOM Level 1 属性返回的是 Node(节点) 而非仅针对 Element(元素)。
TextNode(文本节点,nodeType === 3)。如果使用 nextSibling,获取到的往往是一段看不到的空白文本节点,直接访问 classList 或样式便会引发 TypeError: Cannot read properties of undefined。
现代标准为此专门引入了带有 Element 标识的只读属性,它们在规范底层已经自动滤除了注释(Comment)和文本节点(Text),执行逻辑与 jQuery 保持一致。
二、获取全部兄弟元素:siblings() 的原生实现与步骤
jQuery 的 $(el).siblings() 能够一次性取得同级的所有其他元素,并自动将自身排除在返回结果之外。在原生 JS 中并没有单个内建方法名为 getSiblings(),但借助父子层级关系,实现步骤非常清晰规范。
1. 原生实现的具体操作步骤
- 寻找定位父容器:通过目标元素的
parentElement向上查找直属父节点。如果目标元素尚未挂载到 DOM 树中或为根节点,则安全退出。 - 提取子元素集合:读取
parent.children。这里得到的是一个实时的HTMLCollection,它天生只包含元素节点,自动规避了换行文本干扰。 - 剔除当前基准元素:将类数组集合通过
Array.from()转换为数组实例,借助filter排除child === el的自身项。 - 按需执行选择器过滤:如果传入了 CSS 选择器参数,继续调用元素原生的
matches(selector)进行二次筛选。
2. 生产级原生 siblings 工具函数封装
为了在项目中随处复用,可以将上述步骤封装为一个高效、容错的纯工具函数:
/**
* 获取指定元素的所有兄弟元素(原生实现)
* @param {Element} el - 当前基准 DOM 元素
* @param {string|null} selector - 可选的 CSS 选择器过滤条件
* @returns {Element[]} 兄弟元素数组
*/
function getSiblings(el, selector = null) {
if (!el || !el.parentElement) {
return [];
}
// 提取父节点下所有子元素,转为数组并滤除自身
const siblings = Array.from(el.parentElement.children).filter(child => child !== el);
// 若无选择器限制,直接返回
if (!selector) {
return siblings;
}
// 使用标准 matches API 进行选择器过滤
return siblings.filter(child => child.matches(selector));
}
// 使用示例对比:
// jQuery: $('#tab-btn').siblings('.active').removeClass('active');
// 原生写法:
const currentBtn = document.querySelector('#tab-btn');
getSiblings(currentBtn, '.active').forEach(sibling => {
sibling.classList.remove('active');
});
三、获取区间兄弟元素:nextAll() 与 prevAll() 的原生写法
在数据表格高亮、问卷进度展示等场景中,经常需要选择“当前项之后的所有项”或“当前项之前的所有项”。jQuery 提供了 .nextAll() 与 .prevAll() 实现这种单向扫描。
1. 为什么原生没有 nextAll 属性?
W3C DOM 规范主要提供基础的图结构导航属性(父、子、前、后)。像区间扫描这类复合操作,原生鼓励开发者根据具体数据结构利用简单的循环(while loop)在常数空间内完成累加,避免在底层维护多套重叠的集合属性。
2. nextAll 与 prevAll 的原生函数实现
利用 nextElementSibling 与 previousElementSibling 作为迭代指针,构建单向遍历非常直观:
/**
* 获取当前元素之后的所有同级兄弟元素
*/
function getNextSiblings(el, selector = null) {
const results = [];
let current = el ? el.nextElementSibling : null;
while (current) {
if (!selector || current.matches(selector)) {
results.push(current);
}
current = current.nextElementSibling;
}
return results;
}
/**
* 获取当前元素之前的所有同级兄弟元素
* 注意:jQuery 的 prevAll 默认按从近到远的逆序排列,若需与之一致,直接 push 即可
*/
function getPrevSiblings(el, selector = null) {
const results = [];
let current = el ? el.previousElementSibling : null;
while (current) {
if (!selector || current.matches(selector)) {
results.push(current);
}
current = current.previousElementSibling;
}
return results;
}
prevAll() 返回的元素顺序是从基准点向页面开头逆向推进的(即 index 0 是距离最近的前一个元素)。上述原生 while 循环收集方式与 jQuery 默认的遍历序完全一致。若需要按照在 DOM 树中从上到下的自然文档序,可对数组调用 results.reverse()。
四、返回值特征、错误容忍度与性能考量
除了书写方式上的不同,原生 JS 与 jQuery 在返回值类型、容错设计和运行时性能上也有着本质差异:
1. 集合包装 vs 纯净对象
- jQuery 的包装特性:无论匹配到一个、多个还是零个元素,所有方法一律返回一个包装过的 jQuery 类数组对象。这种设计的好处是支持链式调用(如
$(el).next().addClass('x')),即使没有找到下一个元素,也不会抛出空指针异常,而是静默失败。 - 原生 JS 的精准返回:单元素属性(如
nextElementSibling)在未匹配时返回null;多元素操作返回原生Array。原生返回的是裸 DOM 元素对象,具备极高的内存利用率,同时可以无缝对接 ES6+ 语法。
2. 空值保护与现代可选链(?.)
在原生 JS 中操作兄弟元素时,必须具备防御性编程意识。在现代 JavaScript(ES2020+)中,推荐使用可选链操作符(Optional Chaining)来实现类似 jQuery 的安全链式调用:
// 原生现代空值安全调用写法
const current = document.querySelector('.step-active');
// 如果没有下一个元素,?. 运算符直接返回 undefined,不会抛出 TypeError
current?.nextElementSibling?.classList.add('step-ready');
3. 性能考量与架构演进
随着现代浏览器对 DOM 规范的高质量实现,原生 API(如 parentElement、children、nextElementSibling)均由浏览器内核 C++ 原生提供,其查询性能显著超越经过 JavaScript 运行时封装的 jQuery 包装器。在大型长列表(如虚拟滚动、复杂表格)的渲染与交互中,使用纯原生兄弟元素遍历能够彻底消除 jQuery 包装对象的内存分配开销,大幅降低 GC(垃圾回收)压力。
五、全景对照与实用选择建议
| 功能需求 | jQuery 语法 | 原生 JS 推荐写法 | 兼容性与注意事项 |
|---|---|---|---|
| 下一个兄弟元素 | $(el).next() |
el.nextElementSibling |
现代浏览器及 IE9+ 全面支持,无元素时返回 null |
| 上一个兄弟元素 | $(el).prev() |
el.previousElementSibling |
现代浏览器及 IE9+ 全面支持,无元素时返回 null |
| 所有同级兄弟 | $(el).siblings() |
[...el.parentNode.children].filter(c => c !== el) |
现代标准,返回纯原生数组,可直接使用 map/forEach |
| 之后所有兄弟 | $(el).nextAll() |
while 循环沿 nextElementSibling 累加 |
原生遍历性能极高,无多余封装层 |
| 之前所有兄弟 | $(el).prevAll() |
while 循环沿 previousElementSibling 累加 |
注意数组元素顺序与文档流自然顺序的转换 |
| 带选择器筛选 | $(el).siblings('.cls') |
在过滤逻辑中加入 child.matches('.cls') |
matches() 支持任意合法 CSS 选择器表达式 |
总结而言,在维护已有 jQuery 老旧系统时,继续使用其兄弟查询方法能够保持代码风格统一;但在所有全新的前端模块、轻量级组件、无框架微前端或现代工程项目中,直接选用原生 ElementSibling 属性配合标准数组方法是更为轻量、高效且符合现代 Web 标准的最佳实践。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。