jQuery find() 和 children() 有什么区别

教程14小时前发布 jdkjadmin
0 00
jQuery find() 和 children() 有什么区别

jQuery find() 和 children() 有什么区别

在 jQuery 中,find() 和 children() 都能从已有元素集合继续查找元素,但它们查找的层级不同:find() 查找所有层级的后代元素,children() 只查找直接子元素。

这一区别看起来简单,却会影响组件定位、列表筛选和事件处理。把两者混用,常见结果是选中了嵌套层里的元素、漏掉真正需要的节点,或者因为选择器不符合预期而得到空集合。本文从查找范围、参数、适用场景和安全边界逐项说明。

先看最核心的区别

假设页面结构如下:

<ul class="menu">
  <li class="item">
    一级项目
    <ul>
      <li class="item">二级项目</li>
    </ul>
  </li>
</ul>

下面两种写法得到的结果不同:

var $menu = $(".menu");

$menu.children(".item"); // 只得到外层 ul 的直接子 li,共 1 个
$menu.find(".item");     // 得到外层和内层的 li,共 2 个
方法查找范围是否只看下一层
.children([selector])当前集合中每个元素的直接子元素是
.find(selector)当前集合中每个元素的所有后代元素否

这里的“后代”包括子元素、孙元素以及更深层的元素。两种方法都不会把起始元素本身作为查找结果。例如,$(".menu").find(".menu") 不会因为起始元素本身带有 menu 类而返回它。

find():查找任意深度的后代

.find(selector) 从当前 jQuery 集合中的每个元素向下查找,把所有符合选择器的后代元素组成新的 jQuery 集合。它适合处理目标元素嵌套层级不固定,或者业务上确实需要扫描整个子树的情况。

例如,一个表单项内部可能因页面结构调整而增加包装层,但需要找到其中所有错误提示:

<form id="profileForm">
  <div class="field">
    <label>昵称</label>
    <div class="field-body">
      <span class="error">昵称不能为空</span>
    </div>
  </div>
</form>

var $errors = $("#profileForm").find(".error");
console.log($errors.length); // 1

只要 .error 元素在表单内部,无论它位于第几层,find() 都能找到它。要注意的是,这也意味着它可能找到多个层级中的同类元素。如果只想取紧挨着容器的那一层,应该考虑 children()。

children():只查找直接子元素

.children() 只检查当前元素的下一层子元素,不会继续向下搜索。它适合表达明确的结构关系,例如“列表容器的直接项目”“组件根节点的直接面板”。

<div id="tabs">
  <button class="tab">概览</button>
  <div class="tab-content">
    <button class="tab">内容中的按钮</button>
  </div>
</div>

var $tabs = $("#tabs").children(".tab");
console.log($tabs.length); // 1

如果把最后一行改成 $("#tabs").find(".tab"),则会得到外层的选项卡按钮和内容区域里的按钮。如果业务规则限定“属于这个选项卡容器的直接按钮”,使用 children() 更能准确表达意图,也不容易受内部嵌套结构影响。

children() 返回的是元素子节点,不是所有 DOM 子节点。文本节点和注释节点不会出现在结果中。因此,以下 HTML 中的换行、空格和文本内容不会影响 children() 的元素数量:

<div id="box">
  一段文字
  <span>子元素</span>
  <!-- 注释 -->
</div>

console.log($("#box").children().length); // 1

参数怎么写,限制是什么

find() 的常见形式是 .find(selector),其中 selector 是选择器字符串。它用于匹配后代元素;不传参数不是查找所有后代的写法,应传入一个能匹配目标的选择器。

children() 的选择器参数是可选的:

  • .children():返回全部直接子元素。
  • .children(selector):只返回符合选择器的直接子元素。

例如:

var $container = $("#container");

var $allDirectChildren = $container.children();
var $directButtons = $container.children("button");
var $allNestedButtons = $container.find("button");

传给这两个方法的选择器通常使用 CSS 选择器语法,例如类、ID、标签、属性选择器和组合选择器:

$container.find(".card .title");             // 后代中匹配该组合的元素
$container.children("li.active");             // 直接子 li 中带 active 类的元素
$container.find("[data-role='message']");     // 后代中的属性匹配元素
$container.children(":first");                // 直接子元素集合中的首个匹配项

jQuery 还支持部分 jQuery 专有的选择器扩展,例如 :visible。这类扩展不一定是原生 CSS 选择器;如果代码需要兼容不同的 jQuery 版本或依赖原生 DOM API,应确认所用选择器的支持范围。复杂筛选也可以拆开写,例如先查找,再用 filter() 筛选。

选择器语法必须有效。动态拼接了不合法的选择器时,jQuery 可能抛出选择器语法错误,而不是安静地返回空集合。选择器字符串也不是普通文本匹配:传入 "item" 表示匹配名为 item 的元素,不表示查找文本内容为“item”的元素。

两种方法都支持链式调用

find() 和 children() 都返回新的 jQuery 集合,可以继续调用其他 jQuery 方法。它们不会把找到的元素改成当前集合,也不会改变 DOM 结构。

$("#menu")
  .children("li")
  .addClass("top-level");

$("#panel")
  .find(".message")
  .text("已更新");

第一段代码只给 #menu 的直接 li 添加类;第二段则修改 #panel 内所有层级的 .message 元素。若集合为空,链式调用通常仍然可以执行,只是不会对任何元素产生效果。调试时可以先检查数量:

var $items = $("#menu").children("li");

console.log($items.length);
console.log($items.get());

多个起始元素时的行为

如果当前 jQuery 集合中有多个起始元素,find() 和 children() 都会对集合中的元素逐个查找,并把匹配结果收集起来。因此,调用结果的数量不一定等于起始元素的数量。

var $sections = $(".section");

var $directHeadings = $sections.children("h2");
var $allHeadings = $sections.find("h2");

第一行结果包含每个 .section 的直接子 h2;第二行包含各个 .section 内所有层级的 h2。如果某个元素同时落在多个起始元素的后代范围内,或起始集合存在重叠,最终结果还可能涉及重复元素的整理。不要仅凭起始集合大小推断结果数量;当结果数量很重要时,直接检查集合或根据 DOM 结构设计更明确的查找范围。

如何按场景选择

场景一:只处理容器的直接项目

列表、菜单、导航栏的一级项目,通常应优先使用 children()。这样内部嵌套列表即使也包含同类项目,也不会被一并处理。

var $menu = $("#mainMenu");

$menu.children("li").each(function () {
  console.log($(this).text());
});

场景二:查找子树里所有匹配元素

当容器内部结构有多层,而任务是清理、统计或更新所有匹配项时,使用 find()。

$("#results").find(".temporary").remove();

这会删除 #results 内所有层级的 .temporary 后代。如果只想删除它的直接子元素,应改用:

$("#results").children(".temporary").remove();

场景三:依赖直接结构关系的组件

组件代码常需要区分根节点的直接子区域和区域内部的同名元素。例如,折叠面板根节点的直接子标题才是面板标题。使用 children() 可以把这个结构约束写进代码中,避免误选标题内容里的其他同名元素。

场景四:结构不稳定,目标不保证紧邻容器

如果目标元素可能被包装层包住,而且这些包装层不是业务约束的一部分,使用 find() 更合适。相反,如果层级变化意味着页面结构已经不符合预期,使用 children() 能更早暴露结构变化,避免代码悄悄处理错误的元素。

常见错误与排查方式

错误一:把 find() 当成只找第一层

find(".item") 不会自动只返回最近一层或第一个匹配项,它会收集所有深度上符合条件的后代。若嵌套列表中出现重复类名,结果可能比预期多。明确需要直接子元素时,应使用 children();需要第一个匹配项时,可以在查找之后使用 first(),但要先确认“第一个”的顺序符合业务要求。

var $firstError = $("#form").find(".error").first();

错误二:以为 children() 会继续往下找

下面的结构中,.children(".error") 找不到位于 .field-body 内的错误提示,因为它不是 #form 的直接子元素。

<form id="form">
  <div class="field">
    <div class="field-body">
      <span class="error">必填</span>
    </div>
  </div>
</form>

$("#form").children(".error"); // 空集合
$("#form").find(".error");     // 找到提示

错误三:忘记起始元素本身不在结果里

假如 $("#box") 本身有 item 类,那么 $("#box").find(".item") 仍然不会包含 #box。若需要检查当前元素本身是否匹配,可使用 is();如果要把自身和后代都纳入筛选,需明确组合两种范围,而不是期待 find() 自动包含自身。

var $box = $("#box");

if ($box.is(".item")) {
  console.log("起始元素本身匹配");
}

var $items = $box.find(".item");

错误四:混淆子元素和子节点

jQuery 的 children() 选出元素,不会返回文本节点。需要遍历文本、注释等所有节点时,children() 并不适用;原生 DOM 中可以使用 childNodes,而只要元素节点时可使用 children 属性。两者的结果类型和接口不同,不要把 jQuery 集合与原生节点列表混为一谈。

错误五:选择器范围写得过宽

例如在整个页面上执行 $("button"),会匹配页面中的所有按钮;而 $("#dialog").find("button") 把查找限制在对话框后代范围内。确定了组件根节点后,从根节点继续查找,通常更容易维护,也能避免误操作其他组件。

动态选择器与安全注意事项

选择器是查询语法,不是输入清洗机制,也不是安全边界。把不可信字符串拼进选择器,可能导致语法错误、意外匹配,或改变查询范围。不要把用户输入直接拼接成选择器,也不要认为“用了 jQuery 选择器”就已经防止了跨站脚本攻击(XSS)。

例如,需要按动态 ID 查找元素时,应该对要放进 CSS 选择器中的标识符进行正确转义。较新的 jQuery 提供 $.escapeSelector();该方法从 jQuery 3.0 起提供。若项目版本较旧,应查阅对应版本的文档,或采用经过验证的转义方式,不要假设所有版本都包含它。

var id = "item:one";

if ($.escapeSelector) {
  var $target = $("#" + $.escapeSelector(id));
}

这里转义的目的是让动态 ID 能作为选择器标识符使用,并不等于把任意用户输入变成可信内容。$.escapeSelector() 适用于 CSS 选择器中的标识符,不应被当作 HTML 转义函数、URL 编码函数或通用输入验证器。更好的设计往往是避免拼接选择器:先在已限定的元素集合中查找,再用 filter() 对元素属性或数据进行比较。

var wantedId = "item:one";

var $target = $("#list")
  .find("[data-item-id]")
  .filter(function () {
    return $(this).attr("data-item-id") === wantedId;
  });

另外,查找到元素后,如果要把外部输入写入页面,应优先使用 .text() 设置纯文本,而不是把未经处理的内容传给 .html()。选择器转义与 HTML 输出编码解决的是不同问题;前者不会自动保护后者。

性能与可读性

find() 需要在起始元素的后代范围内查找,范围越大、结构越复杂,通常需要检查的元素越多。若只需要下一层,使用 children() 不仅语义准确,也能避免不必要地搜索更深层级。实际性能还取决于 DOM 规模、选择器和 jQuery 版本,不应只靠方法名称推断具体耗时。

可以从“代码要表达什么关系”出发选择方法:需要所有后代,写 find();只允许直接子元素,写 children()。不要为了让结果“目前刚好正确”而使用更宽泛的查找范围。明确范围通常比之后用复杂条件补救更容易维护。

一个完整的小例子

下面的示例演示:只切换列表中的一级条目,同时统计容器内所有深层提示。将 jQuery 加载到页面后,直接运行这段脚本即可观察结果。

<div id="demo">
  <ul class="menu">
    <li class="item">
      首页
      <ul>
        <li class="item">子页面</li>
      </ul>
    </li>
    <li class="item">关于</li>
  </ul>

  <div class="messages">
    <span class="message">第一条</span>
    <div><span class="message">嵌套的一条</span></div>
  </div>
</div>

<script>
  var $demo = $("#demo");

  // 菜单的直接项目:2 个,不包括嵌套菜单里的项目
  var $topItems = $demo.find(".menu").children(".item");
  $topItems.addClass("top-level");

  // 消息区域下所有层级的提示:2 个
  var $messages = $demo.find(".messages").find(".message");

  console.log("一级菜单项目:", $topItems.length);
  console.log("全部消息:", $messages.length);
</script>

这段代码中,先用 find(".menu") 在组件内部定位菜单,再用 children(".item") 限定一级项目;消息则明确需要统计所有层级,因此使用 find(".message")。组合使用两种方法时,每一步都对应一个清晰的结构关系。

简明结论

  • find(selector):查找当前集合中每个元素的所有后代,不包括元素自身。
  • children([selector]):查找直接子元素;选择器可省略,不包括更深层的后代。
  • 两者得到的都是 jQuery 集合;匹配不到时得到空集合。
  • 需要处理整个子树时选 find();只允许下一层时选 children()。
  • 选择器不是输入净化工具。动态拼接时要正确转义或改用属性筛选;向页面输出不可信内容时仍需单独处理。

记住“find() 看所有后代,children() 只看直接子元素”,再结合实际 DOM 结构确认边界,就能避开这两个方法最常见的误用。

© 版权声明

相关文章