为什么jQuery或一个DOM方法,如getElementById找不到元素?

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

示例问题包括:

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

Uncaught TypeError: Cannot set property '…` null
无法设置null属性(设置'…')
无法读取属性…` null
无法读取null属性(读取'…')

最常见的形式是:

Uncaught TypeError:无法设置null
的属性“onclick” 无法读取null
的属性'addEventListener' 无法读取属性'style' null

179628 次浏览

当脚本运行时,您试图查找的元素不在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 <中的bug实现;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>

窗口:加载事件

DOMContentLoaded和其他资源(如样式表和图像)加载后,load事件触发。由于这个原因,它的发射时间比我们预期的要晚。不过,如果你考虑的是像IE8这样的老浏览器,这种支持几乎是通用的。当然,你可能想要填充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的# EYZ0

DOMContentLoadedwindow:load都有自己的注意事项。jQuery的ready()提供了一个混合的解决方案,尽可能使用DOMContentLoaded,必要时切换到window:load,如果DOM已经完成,则立即触发它的回调。

你可以把你的ready处理器直接传递给jQuery作为$(handler),例如:

<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的# EYZ0

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>

因为您要查找的元素(目前)还不存在于文档中。


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

可能的原因

一个元素不存在的原因有三个:

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

    如果您使用getElementById,请确保您使用document.getElemntById("the-id")5给出元素的ID(例如,document.getElemntById("the-id"))。如果你正在使用一个接受CSS选择器的方法(如querySelector),确保你在ID之前包含了#,以表明你正在寻找一个ID(如document.querySelector("#the-id"))。你必须document.getElemntById("the-id")6使用#getElementByIddocument.getElemntById("the-id")7使用它与querySelector和类似。还要注意,如果ID中有在document.getElemntById("the-id")8中无效的字符(例如.;包含.字符的id属性是糟糕的实践,但有效),当使用querySelector (document.getElemntById("the-id")2)时,您必须转义这些,但当使用getElementById (document.getElemntById("the-id")4)时则不必。

  2. 元素不存在目前你调用getElementById

  3. 元素不在您正在查询的文档中,尽管您可以在页面上看到它,因为它在iframe(它自己的文档)中。在搜索包含它们的文档时,不会搜索iframes中的元素。

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

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

考虑下面的例子:

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


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

div出现script。在脚本执行时,元素然而,不存在,getElementById将返回null

jQuery

这同样适用于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元素在脚本执行时都是可用的)。

其他解决方案包括监听# EYZ0 < em > <一口> [MDN] < /一口> < / em ># EYZ1 < em > <一口> [MDN] < /一口> < / em >事件。在这些情况下,将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对# EYZ2 < em > <一口>[医生]< /一口> < / em >所做的事情。所有影响DOM元素的jQuery代码都应该在该事件处理程序中。

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

当使用jQuery时,我们所做的几乎所有事情都是读取或操作文档对象模型(DOM),我们需要确保一旦DOM准备好,我们就开始添加事件等。

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

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

或者你也可以使用简写语法:

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

两者是等价的。

正如@FelixKling指出的那样,最有可能的情况是您正在寻找的节点(目前)还不存在。

然而,现代开发实践通常可以在文档树之外操作文档元素,可以使用DocumentFragments,也可以直接分离/重新附加当前元素。这样的技术可以作为JavaScript模板的一部分,或者在有问题的元素被大量修改时避免过度的重绘/回流操作。

类似地,在现代浏览器中推出的新的“Shadow DOM”功能允许元素成为文档的一部分,但不能按文档查询。getElementById及其所有兄弟方法(querySelector等)。这样做是为了封装功能并具体地隐藏它。

但是,您所寻找的元素很可能(还)不在文档中,您应该按照Felix的建议去做。但是,您也应该意识到,这越来越不是一个元素可能无法找到(无论是暂时的还是永久的)的唯一原因。

基于id的选择器不能工作的原因

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

解决方案

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

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

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

如果您试图访问的元素在iframe中,并且您试图在iframe的上下文之外访问它,这也会导致它失败。

如果你想在iframe中获取一个元素,你可以找到在这里

如果脚本执行顺序不是问题,另一个可能的原因是元素没有被正确选择:

  • getElementById要求传递的字符串是ID 逐字,没有其他。如果传入的字符串前缀为#,并且ID不是以#开头,则什么都不会被选中:

      <div id="foo"></div>
    
      // Error, selected element will be null:
    document.getElementById('#foo')
    // Fix:
    document.getElementById('foo')
    
  • 类似地,对于getElementsByClassName,不要以.作为传递字符串的前缀:

      <div class="bar"></div>
    
      // Error, selected element will be undefined:
    document.getElementsByClassName('.bar')[0]
    // Fix:
    document.getElementsByClassName('bar')[0]
    
  • 使用querySelector、querySelectorAll和jQuery,要匹配具有特定类名的元素,请直接在类前放置.。类似地,要匹配具有特定ID的元素,在ID前直接放置#:

      <div class="baz"></div>
    
      // Error, selected element will be null:
    document.querySelector('baz')
    $('baz')
    // Fix:
    document.querySelector('.baz')
    $('.baz')
    

    在大多数情况下,这里的规则与CSS选择器的规则相同,可以在在这里中详细看到。

  • 要匹配一个有两个或多个属性的元素(比如两个类名,或者一个类名和一个data-属性),将每个属性的选择器放在选择器字符串中,没有用空格分隔它们(因为空格表示后代选择器)。例如,选择:

      <div class="foo bar"></div>
    

    使用查询字符串.foo.bar。选择

      <div class="foo" data-bar="someData"></div>
    

    使用查询字符串.foo[data-bar="someData"]。选择下面的<span>:

      <div class="parent">
    <span data-username="bob"></span>
    </div>
    

    使用# EYZ0。

  • 以上所有的大写和拼写很重要。如果大小写不同,或者拼写不同,元素将不会被选中:

      <div class="result"></div>
    
      // Error, selected element will be null:
    document.querySelector('.results')
    $('.Result')
    // Fix:
    document.querySelector('.result')
    $('.result')
    
  • 你还需要确保方法有正确的大写和拼写。使用其中之一:

    $(selector)
    document.querySelector
    document.querySelectorAll
    document.getElementsByClassName
    document.getElementsByTagName
    document.getElementById
    

    其他任何拼写或大写都不行。例如,document.getElementByClassName将抛出一个错误。

  • 确保你向这些选择器方法传递了一个字符串。如果你传递一个不是字符串的东西给querySelectorgetElementById,等等,它几乎肯定不会工作。

  • 如果你想选择的元素上的HTML属性被引号包围,它们必须是普通的直引号(单引号或双引号);如果您试图按ID、类或属性选择,像这样的大括号引号将不起作用。