ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

jQuery遍历方法实战:从parent到siblings的DOM导航

jQuery遍历方法实战:从parent到siblings的DOM导航 接手过一个十年前的老管理系统前端交互全靠jQuery撑着。那段维护经历让我把parent()、children()、siblings()这些遍历方法重新盘了一遍。说实话在原生querySelectorAll和各类前端框架已经相当成熟的今天还在写jQuery的人多少会被质疑“过时”但凡是碰过遗留系统、模板化老项目的人心里都清楚jQuery的遍历方法不仅没死在日常开发和bug修复中反而是最高频用到的工具之一。这篇内容适合正在维护老项目的同学也适合想系统梳理jQuery遍历体系的新手。我把平时用得最多、踩坑最多的遍历方法分类拆开讲结合高频热搜词第一个子元素、根据name获取对象、获取HTML中的ID做完整演示。讲清楚每个方法底层在干什么、为什么这么写、选了它会有什么连带影响。1. 为什么jQuery遍历方法至今仍是高频工具1.1 老项目维护场景下的真实需求先聊个现实问题很多内部管理系统、旧版电商后台、早期CMS模板代码库里大量充斥着$(.item).each()和$(this).next()这类写法。你没法要求客户因为你“想用Vue重构”就给你排期维护这种项目时看懂并熟练使用遍历方法就是基本功。实际需求往往是这样的表格中点击某一行按钮需要找到这行的下一个输入框赋值表单提交时要把所有checkbox勾选状态收集起来菜单展开时需要让当前项的兄弟节点全部收起。这些需求用原生JS写起来不是不行但代码量会明显增加还得自己处理兼容性。jQuery的遍历方法把“找节点”这件高频操作封装成了语义化方法减少临时变量和索引计算读代码时一眼就能看出你要操作的是父级、子级还是兄弟节点。1.2 选择器驱动的设计思路与优势jQuery遍历方法的核心设计思路可以用一句话概括一切从选择器出发结果仍是jQuery对象。它和原生DOM API最大的区别是返回值的统一——原生getElementById返回单个DOM节点querySelectorAll返回静态NodeList而jQuery方法几乎都返回一个可链式调用的jQuery集合对象。这意味着你可以连续操作$(ul).children(li).eq(1).addClass(active)。每一步都返回新的jQuery对象不需要像原生那样反复声明变量。还有个隐性优势是隐式迭代如果jQuery对象集合里有5个元素调用.addClass()方法会同时作用于5个元素不需要显式for循环。跟原生API对比一下更直观操作需求原生写法jQuery写法找所有子元素el.children$(el).children()找下一个兄弟el.nextElementSibling$(el).next()找满足条件的最近祖先循环 判断$(el).closest(.box)过滤出可见元素循环 computedStyle$(el).filter(:visible)这种封装带来的最大价值是心智负担降低。索引计算、断点判断、兼容性处理都交给了底层我们专注于业务关系。2. 核心遍历方法详解与选型思路2.1 祖先与后代遍历的层级逻辑层级遍历是我用得最多的一类也是最容易混乱的一类。往上走的三个方法经常让新手混淆parent()只向上查找一级父节点parents()则一直向上追溯到文档根节点返回所有祖先节点集合closest()比较特殊它从当前元素自身开始向上查找第一个匹配选择器的祖先元素找到了就停下。选型逻辑基于语义只想拿到直接父级用parent()想给整条祖先链做统一处理用parents()想实现“点击任意子元素关闭最近的弹窗容器”closest()是唯一合理选择。还有一个不常用但有时能救命的offsetParent()返回离当前元素最近的已定位的祖先元素做挂载浮层计算坐标时有用。向下遍历也有讲究。children()只找直接子元素不递归find()会向下遍历所有后代节点是目前查找效率最高的方式之一contents()连文本节点和注释节点一起返回做HTML解析或清理时用得到。我在实际操作中摸索出的经验是遍历层级一定要遵循“就近优先”原则。能用children()明确范围就不要用find()去污染结果集。比如树形菜单的当前层级子节点用children()才能保证“只操作同级目录”用find()会把所有层级的后代都捞出来导致逻辑错乱。2.2 兄弟节点遍历的边界处理兄弟遍历是表单交互的常客。next()和prev()分别获取紧邻的下一个和上一个兄弟元素nextAll()和prevAll()获取后面或前面的所有兄弟siblings()则把除了自己以外所有兄弟一次性返回。这些方法都支持选择器参数可以在获取的同时过滤比如next(.active)只匹配class为active的兄弟节点。边界场景要注意如果当前节点是最后一个子元素next()返回空对象调用方法不会报错但结果为空集合代码里需要做判断。看一个我维护过的折叠面板案例$(.panel-header).on(click, function() { // 当前面板内容区 var $content $(this).next(.panel-content); // 收起其他面板内容 $(this).siblings(.panel-header).next(.panel-content).slideUp(); // 展开当前 $content.slideToggle(); });这段代码的精髓在于siblings()配合next()连用先找当前头部外面的兄弟头部再通过next()触达到它们各自的内容区。如果不用链式兄弟遍历就得先存变量再逐个操作代码会冗长很多。兄弟遍历还有个细节文本节点不参与。next()只会找元素节点不会因为空行或注释导致找不到目标。这正是它比“用原生nextSibling再跳过文本节点”要省心的地方。2.3 过滤型遍历的语义理解过滤型遍历是让代码变得优雅的关键。eq(index)从0开始取指定位置的元素first()和last()是取首尾的语法糖filter(selector)保留匹配条件的元素not(selector)反过来排除has()是“筛选出包含某子元素的元素”。我经常遇到的问题是有人分不清filter()和find()的区别。一句话讲透filter是“我本来就在结果集里按条件筛选我自己”find是“我从结果集的子子孙孙里捞新人”。它们的操作集合完全不同。另外一个是eq()和:eq()选择器的区别。$(li:eq(2))是在选择阶段就过滤效率略低且语义混在字符串里$(li).eq(2)是先生成集合再通过方法索引。我自己优先使用方法形式因为可以利用变量动态传参var index getCurrentIndex(); $(.menu-item).eq(index).addClass(current);动态拼接选择器字符串:eq( index )虽然也能跑但可读性和维护性都不如方法形式干净。2.4 链式调用与 end() 状态回溯链式调用是jQuery遍历方法最迷人的特性。每一步方法调用都会生成一个新的jQuery对象end()方法则可以回退到上一次调用前的状态。这一度是jQuery面试题里的常见考点也是实际代码中写出“阅读体验极佳”段落的关键。举个实际场景一个商品列表点击对应按钮要同时修改卡片边框、图标和描述文字并且在操作完成后把整个列表的背景色恢复。$(.product-card) .find(.card-title).text(已选中).end() .find(.card-icon).addClass(highlight).end() .find(.card-desc).hide().end() .css(background, #ffffff);find()之后用end()回退到.product-card集合才能继续下一组查找。如果不使用end()就得用变量缓存$(.product-card)也可以达到同样效果但少了行云流水的感觉。注意end()不是万能的。如果链中出现了addClass()、css()这类不改变集合的方法end()依旧会回到上一次“产生新集合”的方法之前。想精确控制状态可以用pushStack()但实际项目中很少用。懂end()能帮你读懂别人压缩过的代码排查问题时定位到当前jQuery对象指向哪里。3. 实操环节3个高频场景完整演示3.1 获取第一个子元素的四种写法对比热搜词里“jquery 第一个子元素”是个经典需求。我罗列四种实现方式并备注各自的语义差别写法适用场景潜在问题$(ul li:first-child)纯CSS选择器语义匹配父元素下的第一个li多个ul会命中多个li$(ul li:first)整个文档范围内第一个li只在确定只有一个ul时用$(ul).children().first()直接子元素中的第一个不包含孙级元素$(ul li).eq(0)所有li中的第一个集合顺序依赖文档顺序这里最容易出错的是:first-child和:first的区别。li:first-child是“作为父节点第一个子元素的li”如果页面里有10个ul会匹配到10个lili:first只会匹配一个。以下是我实际维护导航高亮时用到的写法// 选中当前tab容器下的第一个tab var $tabs $(.tabs-container).children(.tab-item); var $firstTab $tabs.first(); $firstTab.addClass(tab-active);3.2 根据name属性精确获取对象“jquery根据name获取对象”对应的核心方法是属性选择器$([namexxx])。用name属性定位在表单操作中特别常见因为name是表单提交的标识用它找控件再自然不过。// 获取用户名字段 var $username $([nameusername]); // 获取所有复选框 var $checks $(input[namehobby]:checked); // 在同一表单内限定范围 var $form $(#registerForm); var $email $([nameemail], $form);第二行代码里input[namehobby]:checked是属性选择器和状态选择器的组合写法非常实用能把“勾选中的爱好项”一次取完。第四行代码用$([nameemail], $form)限定上下文相当于$form.find([nameemail])。这里要特别强调引号嵌套问题。属性值通常建议用双引号包裹如果属性值本身包含引号或者你正在拼接变量很容易踩坑。正确姿势var fieldName user[email]; var $field $([name fieldName ]);user[email]中有方括号不影响name选择器取值因为选择器语法本身只认最外层属性名与值的匹配规则。如果搞不清楚用filter()兜底也是一种方案var $field $(input).filter(function() { return this.name user[email]; });3.3 获取HTML中的ID并执行相应操作“jquery获取html中的id”包含两类含义一是通过ID选择器选中元素二是读取已有元素的ID属性值。第一种太基础重点说说第二种。实际业务中我们经常需要遍历一组元素读取它们身上的数据标识。ID作为最直接的唯一标识常常承担这个角色$(.delete-btn).on(click, function() { var id $(this).attr(id); // 或更直接的DOM原生方式 var id2 this.id; console.log(准备删除记录ID:, id2); });直接用this.id比包一层jQuery再attr(id)快不少因为它是原生DOM属性读取。但要注意如果元素的ID是通过>$(.item).each(function(index) { var currentId this.id; if (currentId special) { $(this).css(border-color, #c00); } });这里要谨记this指向的是原生DOM对象不是jQuery对象。想调用jQuery方法前得先$(this)包一层反过来如果直接使用this.id、this.value、this.checked等原生属性反而比$(this).attr()更高效。4. 常见问题与排查技巧实录4.1 遍历结果为空时的静默失败jQuery遍历方法最坑的一点是结果为空时不报错后续链式调用悄然失效。新手经常排查半天发现是选择器没匹配到元素。比如$(.not-exist).parent().addClass(active);这段代码不会报任何错但active类永远加不上。排查时可以先打印集合长度确认var $boxs $(.box); console.log($boxs.length); // 0 就说明选择器没命中我习惯在写遍历链前先验证选择器尤其是动态渲染后的节点。如果节点是由Ajax加载的初始DOM里不存在选择器必然失效。这时要么在回调里重新查询要么用事件委托。事件委托经典的on()写法用的是closest()定位$(document).on(click, .dynamic-item, function() { var $card $(this).closest(.card); $card.toggleClass(expanded); });4.2 遍历性能陷阱与缓存遍历方法的性能问题主要体现在过度查询上。一个常见错误是在循环中重复查询选择器// 不推荐的写法 for (var i 0; i 10; i) { $(.item- i).text(更新 i); } // 推荐先把所有节点查出来再遍历 var $allItems $([class^item-]); $allItems.each(function(index) { $(this).text(更新 index); });上述写法不仅减少查询次数还利用jQuery的集合批处理能力。进一步说如果能缩小查询范围遍历性能会提升更明显。$(container).find(.target)永远快过全局$(.target)因为查找起点小。还有一个容易忽略的性能点parents()会一路查到document开销比closest()大。如果只需要找最近的某个祖先无脑用closest()它命中即停。日常开发中养成“能局部查就不全局查能最近找就不挖整条链”的习惯页面元素上千时体感差距会拉开。4.3 jQuery对象与原生DOM对象的混用误区遍历方法返回值是jQuery对象但很多回调函数里的this是原生DOM对象。两者混用会导致“方法不存在”的错误。比如直接this.hide()会报错因为原生DOM没有hide方法而$(this).get(0)又是从jQuery对象反推原生的方式。我见过大量代码在这问题上来回踩。整理成规则想用addClass、attr、on等jQuery方法先$(this)包装想用value、checked、selectedIndex等原生属性直接用this从jQuery对象取原生节点用[index]或.get(index)原生节点集合转jQuery集合用$(domList)整套逻辑理顺之后代码质量提升非常明显。另外在使用each()时回调参数index是数字不要把它当成元素引用。4.4 动态集合与静态集合的差异这个坑带有隐蔽性。$(.item)返回的是一个静态快照在查询那一刻把匹配元素固定下来。如果之后往DOM里新添加了.item旧的jQuery集合不会自动更新。反之很多遍历方法如.children()在每次调用时才会重新获取当前结构所以同一个jQuery对象在不同时间调用children()可能得到不同结果。这带来一个务实建议如果要在多次操作中共享遍历结果可以提前缓存jQuery对象var $list $(.list); // 第一次操作 $list.children().first().addClass(highlight); // 动态添加新子项 $list.append(li新项/li); // 第二次操作时需重新获取children $list.children().last().addClass(highlight);虽然$list还是那个$list但children()每次都会重新遍历当前DOM结构。理解这一点能避开很多“为什么新增元素拿不到”的疑问。5. 从实战角度聊聊遍历方法的使用心得很多时候技术选型不是“最新的就是最好的”而是“解决问题最顺手的才是最好的”。jQuery遍历方法放到今天依然能打是因为它把DOM关系导航做得足够直观。在维护老项目的过程中我总结出几个值得分享的经验。第一命名要语义化。不要简短到没含义$p、$c、$s这样的变量名过了两天自己都看不懂。我习惯用$parentBox、$childItems、$siblingTabs这种带前缀的命名一眼就知道是jQuery对象以及它在节点树中的位置。第二多读压缩过的源码。压缩后的jQuery插件里常见一大串链式调用和end()回溯。把这些代码一行行展开把上下文标记出来比看任何教程都锻炼定位能力。遇到看不懂的遍历链我通常会在中间临时加console.log()打印当前集合的length帮自己判断节点集合在哪一步发生了变化。第三别忽视知识的迁移价值。closest()、find()、filter()这些方法在函数式编程、CSS选择器解析甚至Web Component的closest()原生API中都有影子。把jQuery遍历思想的底层逻辑搞清楚切换到其他技术栈时理解成本会降低很多。最后一个私藏技巧在调试遍历链时可以在链尾加.length直接输出集合大小。比如$(.list).children(.active).length这样在控制台里能快速统计命中数量排查漏选或多选的问题特别高效。别总觉得这是“老技术”就不屑一顾等你被那些还在跑着jQuery的生产环境bug卡住的时候这套知识会帮你快速脱困。
返回列表