为什么 jQuery 或诸如 getElementById 之类的 DOM 方法找不到该元素?

IT技术 javascript jquery dom
2020-12-10 21:10:52

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

示例问题包括:

  • jQuery 静默无法绑定事件处理程序
  • jQuery的“吸气”方法(.val().html().text())返回undefined
  • 返回的标准 DOM 方法会null导致以下几个错误中的任何一个:

遗漏的类型错误:无效的不能设置属性“...”
遗漏的类型错误:无法设置为空的属性(设置“...‘)
遗漏的类型错误:无法读取空的财产’...”
遗漏的类型错误:空的无法读取性能(读 '...')

最常见的形式是:

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

6个回答

当您的脚本运行时,您尝试查找的元素不在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:module

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

将您的脚本设置typemodule,例如:

<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 这样的旧浏览器,那么这种支持几乎是普遍的。当然,您可能需要一个polyfill 用于addEventListener().

<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()

DOMContentLoaded并且window:load各自有各自的注意事项。jQueryready()提供了一种混合解决方案,DOMContentLoaded在可能的情况下使用window:load在必要时进行故障转移,如果 DOM 已经完成,则立即触发其回调。

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

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></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://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></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>

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


对于此答案的其余部分,我将使用getElementById了实施例,但同样适用于getElementsByTagNamequerySelector和任何其它DOM方法,其选择的元件。

可能的原因

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

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

    如果您使用的getElementById是 ,请确保您提供元素的 ID(例如,document.getElemntById("the-id"))。如果您使用的方法接受 CSS 选择器(如querySelector),请确保#在 ID 之前包含 ,以表明您正在查找 ID(例如document.querySelector("#the-id"))。不得使用#with getElementById,而必须使用 withquerySelector和 similar 。还要注意的是,如果该ID在它不在有效的字符CSS标识符(如.id属性包含.的字符是不良的做法,但有效的),你必须使用时,逃生者querySelectordocument.querySelector("#the\\.id"))),但不使用时,getElementByIddocument.getElementById("the.id")) .

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

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

如果问题是原因 3(它在 aniframe或类似的文件中),您需要查看 中的文档,而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

这同样适用于所有带有 jQ​​uery 的选择器。如果您拼错了选择器或者您试图在它们实际存在之前选择它们 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 已正确加载。使用浏览器的开发者工具找出jQuery文件是否找到,如果没有则更正URL(例如在开头添加http:https:方案,调整路径等)

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

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

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

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

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

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

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

两者是等价的。

如果您尝试访问的元素在 an 内部,iframe而您尝试在 the 上下文之外访问它,iframe这也会导致它失败。

如果您想在 iframe 中获取元素,您可以在此处找到方法

基于 id 的选择器不起作用的原因

  1. 指定 id 的元素/DOM 尚不存在。
  2. 该元素存在,但未在 DOM 中注册[在从 Ajax 响应动态附加 HTML 节点的情况下]。
  3. 存在多个具有相同 ID 的元素,这会导致冲突。

解决方案

  1. 尝试在声明后访问该元素,或者使用类似的东西 $(document).ready();

  2. 对于来自 Ajax 响应的元素,使用.bind()jQuery方法。旧版本的 jQuery.live()也是如此。

  3. 使用工具 [例如,浏览器的 webdeveloper 插件] 查找重复的 ID 并将其删除。

正如@FelixKling 指出的那样,最可能的情况是您要查找的节点(尚不存在)。

但是,现代开发实践通常可以使用 DocumentFragments 或简单地直接分离/重新附加当前元素来操作文档树之外的文档元素。此类技术可用作 JavaScript 模板的一部分,或在相关元素被大量更改时避免过多的重绘/回流操作。

类似地,在现代浏览器中推出的新“Shadow DOM”功能允许元素成为文档的一部分,但不能通过 document.getElementById 及其所有同级方法(querySelector 等)进行查询。这样做是为了封装功能并专门隐藏它。

不过,同样,很可能您正在查找的元素根本(尚未)在文档中,您应该按照 Felix 的建议进行操作。但是,您还应该意识到,这越来越不是元素可能无法找到(暂时或永久)的唯一原因。