为什么 jQuery 或 DOM 方法(如 getElementById)找不到元素?

新手上路,请多包涵

document.getElementById , $("#id") 或任何其他 DOM 方法/jQuery 选择器找不到元素的可能原因是什么?

示例问题包括:

  • jQuery 静默绑定事件处理程序失败
  • jQuery“ Getter”方法( .val().html().text() undefined
  • 返回 null 的标准 DOM 方法会导致多种错误:

未捕获的类型错误:无法设置 null 的属性“…”

未捕获的类型错误:无法设置 null 的属性(设置“…”)

未捕获的类型错误:无法读取 null 的属性“…”

未捕获的 TypeError:无法读取 null 的属性(正在读取“…”)

最常见的形式是:

未捕获的类型错误:无法设置 null 的属性“onclick”

未捕获的类型错误:无法读取 null 的属性“addEventListener”

未捕获的类型错误:无法读取 null 的属性“样式”

原文由 Felix Kling 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2k
2 个回答

当您的脚本运行时,您试图查找的元素不在 DOM 中。

依赖 DOM 的脚本的位置对其行为有深远的影响。浏览器从上到下解析 HTML 文档。元素被添加到 DOM 中,脚本(通常)在遇到时执行。 这意味着顺序很重要。 通常,脚本无法找到标记中稍后出现的元素,因为这些元素尚未添加到 DOM。

考虑以下标记;脚本 #1 找不到 <div> 而脚本 #2 成功:

 <script>
  console.log("script #1:", document.getElementById("test")); // null
</script>
<div id="test">test div</div>
<script>
  console.log("script #2:", document.getElementById("test")); // <div id="test" ...
</script>

那你该怎么办?你有几个选择:


选项 1:移动脚本

考虑到我们在上面的示例中看到的情况,一个直观的解决方案可能是简单地将脚本向下移动标记,经过您想要访问的元素。事实上,很长一段时间以来,出于各种原因,将脚本放在页面底部被认为是 最佳实践。以这种方式组织,文档的其余部分将在执行脚本之前被解析:

 <body>
  <button id="test">click me</button>
  <script>
    document.getElementById("test").addEventListener("click", function() {
      console.log("clicked:", this);
    });
  </script>
</body><!-- closing body tag -->

虽然这是有道理的,并且是旧版浏览器的可靠选择,但它是有限的,并且有更灵活、更现代的方法可用。


选项 2: defer 属性

虽然我们确实说过脚本是 “(通常)在遇到它们时执行的”,但 现代浏览器允许您指定不同的行为。如果您要链接外部脚本,则可以使用 defer 属性。

[ defer ,一个布尔属性,] 被设置为向浏览器指示该脚本将在文档被解析之后但在触发之前执行 DOMContentLoaded

这意味着您可以将标记为 defer 的脚本放在任何地方,甚至是 <head> ,它应该可以访问完全实现的 DOM。

 <script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>

请记住…

  1. defer 只能用于外部脚本,即:具有 src 属性的脚本。
  2. 注意 浏览器支持,即:IE < 10 中的错误实现

选项 3:模块

根据您的要求,您可以使用 JavaScript 模块。除了与标准脚本( 此处注明)的其他重要区别之外,模块会自动延迟并且不限于外部源。

将脚本的 type 设置为 module ,例如:

 <script type="module">
  document.getElementById("test").addEventListener("click", function(e) {
    console.log("clicked: ", this);
  });
</script>
<button id="test">click me</button>

选项 4:延迟事件处理

将侦听器添加到在解析文档后触发的事件。

DOMContentLoaded 事件

DOMContentLoaded 在从初始解析完全构建 DOM 后触发,无需等待样式表或图像等内容加载。

 <script>
  document.addEventListener("DOMContentLoaded", function(e){
    document.getElementById("test").addEventListener("click", function(e) {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

窗口:加载事件

load 事件在 DOMContentLoaded 之后触发,并且加载了样式表和图像等其他资源。出于这个原因,它的触发时间比我们预期的要晚。不过,如果您正在考虑使用 IE8 等较旧的浏览器,则支持几乎是普遍的。当然,您可能需要 addEventListener() 的 polyfill

 <script>
  window.addEventListener("load", function(e){
    document.getElementById("test").addEventListener("click", function(e) {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

jQuery 的 ready()

DOMContentLoadedwindow:load 各有注意事项。 jQuery 的 ready() 提供了一个混合解决方案,使用 DOMContentLoaded 在可能的情况下,故障转移到 window:load 如果需要,它的回调已经完成,并且立即触发。

您可以将准备好的处理程序作为 $(handler) 直接传递给 jQuery,例如:

 <script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script>
  $(function() {
    $("#test").click(function() {
      console.log("clicked:", this);
    });
  });
</script>
<button id="test">click me</button>

选项 5:事件委托

将事件处理委托给目标元素的祖先。

当一个元素引发一个事件(假设它是一个 冒泡 事件并且没有什么可以阻止它的传播)时,该元素祖先中的每个父元素,一直到 window ,也会接收到该事件。这允许我们将处理程序附加到现有元素,并在事件从其后代冒泡时对事件进行采样……甚至是在附加处理程序后添加的后代。我们所要做的就是检查事件以查看它是否由所需元素引发,如果是,则运行我们的代码。

通常,此模式是为加载时不存在的元素保留的,或者是为了避免附加大量重复的处理程序。为了提高效率,选择目标元素最近的可靠祖先而不是将其附加到 document

原生 JavaScript

 <div id="ancestor"><!-- nearest ancestor available to our script -->
  <script>
    document.getElementById("ancestor").addEventListener("click", function(e) {
      if (e.target.id === "descendant") {
        console.log("clicked:", e.target);
      }
    });
  </script>
  <button id="descendant">click me</button>
</div>

jQuery 的 on()

jQuery 通过 on() 使这个功能可用。给定事件名称、所需后代的选择器和事件处理程序,它将解析您的委托事件处理并管理您的 this 上下文:

 <script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<div id="ancestor"><!-- nearest ancestor available to our script -->
  <script>
    $("#ancestor").on("click", "#descendant", function(e) {
      console.log("clicked:", this);
    });
  </script>
  <button id="descendant">click me</button>
</div>

原文由 canon 发布,翻译遵循 CC BY-SA 4.0 许可协议

简短而简单: 因为文档中不存在您要查找的元素(还)。


对于这个答案的其余部分,我将使用 getElementById 作为示例,但这同样适用于 getElementsByTagNamequerySelector 方法,并选择任何其他 DOM 方法。

可能的原因

元素可能不存在的三个原因:

  1. 具有传递 ID 的元素在文档中确实不存在。您应该仔细检查传递给 getElementById 的 ID 是否真的与(生成的)HTML 中现有元素的 ID 匹配,并且您没有 拼错 ID(ID _区分大小写_!)。

如果您使用的是 getElementById ,请确保您 提供元素的 ID(例如 document.getElemntById("the-id") )。如果您使用的方法接受 CSS 选择器(例如 querySelector ),请确保在 ID 之前包含 # 以表明您正在寻找 ID (例如, document.querySelector("#the-id") )。您 不得#getElementById 一起使用,并且 必须 将其与 querySelector 等一起使用。另请注意,如果 ID 中包含在 CSS 标识符 中无效的字符(例如 . ; id 包含 . 字符的属性是不好的做法, but valid), you have to escape those when using querySelector ( document.querySelector("#the\\.id") )) but not when using getElementById ( document.getElementById("the.id") ).

  1. 该元素 您调用 getElementById 时不存在。

  2. 该元素不在您正在查询的文档中,即使您可以在页面上看到它,因为它在 iframe (这是它自己的文档)中。当您搜索包含它们的文档时,不会搜索 iframes 中的元素。

如果问题是原因 3(它在 iframe 或类似文件中),您需要查看 iframe 中的文档,而不是父文档,也许通过获取 iframe 元素并使用其 contentDocument 属性访问其文档(仅限同源)。这个答案的其余部分解决了前两个原因。

第二个原因——目前 _还没有_——很常见。浏览器从上到下解析和处理 HTML。这意味着在 DOM 元素出现在 HTML 中之前发生的对该 DOM 元素的任何调用都将失败。

考虑以下示例:

 <script>
    var element = document.getElementById('my_element');
</script>

<div id="my_element"></div>

div 出现 script 之后。在执行脚本的那一刻,元素 还不 存在, getElementById 将返回 null

查询

这同样适用于所有使用 jQuery 的选择器。如果您 拼错 了选择器或者您试图 在它们实际存在之前 选择它们,jQuery 将找不到元素。

一个额外的问题是当你没有找到 jQuery 时,因为你已经加载了没有协议的脚本并且正在从文件系统运行:

 <script src="//somecdn.somewhere.com/jquery.min.js"></script>

此语法用于允许脚本通过 HTTPS 在协议为 https:// 的页面上加载,并在协议为 http:// 的页面上加载 HTTP 版本

它具有尝试加载失败的不幸副作用 file://somecdn.somewhere.com...


解决方案

在调用 getElementById (或任何与此相关的 DOM 方法)之前,请确保您要访问的元素存在,即 DOM 已加载。

这可以通过简单地将您的 JavaScript 放在相应的 DOM 元素之 后来 确保

<div id="my_element"></div>

<script>
    var element = document.getElementById('my_element');
</script>

在这种情况下,您还可以将代码放在结束正文标记 ( </body> ) 之前(所有 DOM 元素将在脚本执行时可用)。

其他解决方案包括监听 load [MDN]DOMContentLoaded [MDN] 事件。在这些情况下,将 JavaScript 代码放在文档中的什么位置并不重要,您只需要记住将所有 DOM 处理代码放在事件处理程序中即可。

例子:

 window.onload = function() {
    // process DOM elements here
};

// or

// does not work IE 8 and below
document.addEventListener('DOMContentLoaded', function() {
    // process DOM elements here
});

有关事件处理和浏览器差异的更多信息,请参阅 quirksmode.org 上的文章

查询

首先确保正确加载了 jQuery。 使用浏览器的开发者工具 查找是否找到了jQuery文件,如果没有则更正URL(例如在开头添加 http:https: 方案,调整路径, ETC。)

收听 load / DOMContentLoaded 事件正是 jQuery 使用 .ready() [文档] 所做的。所有影响 DOM 元素的 jQuery 代码都应该在该事件处理程序中。

事实上, jQuery 教程 明确指出:

由于我们在使用 jQuery 时所做的几乎所有事情都会读取或操作文档对象模型 (DOM),因此我们需要确保在 DOM 准备就绪后立即开始添加事件等。

为此,我们为文档注册一个就绪事件。

>  $(document).ready(function() {
>
> ```

// do stuff when DOM is ready

});


或者,您也可以使用速记语法:

$(function() { // do stuff when DOM is ready });

”`

两者是等价的。

原文由 Felix Kling 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏