555

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

示例问题包括:

  • jQuery 默默地绑定事件处理程序失败
  • jQuery "getter" 方法 ( .val(), .html(), .text()) 返回undefined
  • 一个标准的 DOM 方法返回null导致几个错误中的任何一个:

未捕获的类型错误:无法设置 null 的属性“...”未捕获的
类型错误:无法设置 null 的属性(设置“...”)
未捕获的类型错误:无法读取 null 的属性“...”未捕获的
类型错误:无法读取 null 的属性(阅读 '...')

最常见的形式是:

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

4

6 回答 6

558

当您的脚本运行时,您尝试查找的元素不在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 模块与标准脚本(在此处注明)的其他重要区别之一是模块会自动延迟,并且不限于外部源。

将脚本设置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>

于 2012-01-03T18:06:51.617 回答
163

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


对于这个答案的其余部分,我将使用getElementById示例,但同样适用于getElementsByTagName,querySelector和任何其他选择元素的 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和类似的。另请注意,如果 ID 中包含在CSS 标识符中无效的字符(例如.;id包含字符的属性是不好的做法,但有效),则必须在使用( ).时转义这些字符,但在使用querySelector( document.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:orhttps:方案,调整路径等)

监听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
});

两者是等价的。

于 2012-12-25T08:26:26.553 回答
16

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

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

解决方案

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

  2. 对于来自 Ajax 响应的元素,使用.bind()jQuery 的方法。旧版本的 jQuery 也有.live()同样的功能。

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

于 2014-01-07T12:12:25.600 回答
16

如果您尝试访问的元素位于 an 内部,iframe并且您尝试在 this 的上下文之外访问它,iframe也会导致它失败。

如果您想在 iframe 中获取元素,您可以在此处了解如何操作。

于 2016-01-05T23:34:24.880 回答
14

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

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

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

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

于 2013-12-06T17:29:33.410 回答
12
于 2019-05-10T09:29:26.920 回答