关于javascript:为什么jquery或getElementByID等DOM方法找不到元素?

Why does jQuery or a DOM method such as getElementById not find the element?

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

示例问题包括:

  • jquery未自动绑定事件处理程序
  • jquery"getter"方法(.val().html().text())返回undefined
  • 返回null的标准dom方法,导致以下任何错误:


    Uncaught TypeError: Cannot set property '...' of null
    Uncaught TypeError: Cannot read property '...' of null

    最常见的形式是:

    Uncaught TypeError: Cannot set property 'onclick' of null

    Uncaught TypeError: Cannot read property 'addEventListener' of null

    Uncaught TypeError: Cannot read property 'style' of null


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

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

考虑以下标记;脚本1未能找到,而脚本2成功:

1
2
3
4
5
  console.log("script #1: %o", document.getElementById("test")); // null

test div

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

那么,你应该怎么做?你有几个选择:

选项1:移动脚本

将脚本进一步移到页面下方,就在结束正文标记之前。以这种方式组织,在执行脚本之前解析文档的其余部分:

1
2
3
4
5
6
7
8
<body>
  <button id="test">click me</button>
 
    document.getElementById("test").addEventListener("click", function() {
      console.log("clicked: %o", this);
    });
 
</body><!-- closing body tag -->

注意:将脚本放在底部通常被认为是最佳实践。

方案二:jquery的ready()

使用ready()将脚本延迟到DOM完全解析为止:

1
2
3
4
5
6
7
8
9
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">

  $(document).ready(function() {
    $("#test").click(function() {
      console.log("clicked: %o", this);
    });
  });

<button id="test">click me</button>

注意:您可以简单地绑定到DOMContentLoadedwindow.onload,但每个都有其警告。jquery的ready()提供了一个混合解决方案。

选项3:事件委托

Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.

当一个元素引发一个事件(假设它是一个冒泡事件,并且没有任何东西停止它的传播)时,该元素祖先中的每个父元素也会接收该事件。这允许我们将一个处理程序附加到一个现有的元素和示例事件,因为它们从其后代中冒泡出来…即使是在附加了处理程序之后添加的。我们所要做的就是检查事件,看它是否由所需的元素引发,如果是的话,运行我们的代码。

jquery的on()为我们执行该逻辑。我们只提供一个事件名、所需子代的选择器和一个事件处理程序:

1
2
3
4
5
6
7
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">

  $(document).on("click","#test", function(e) {
    console.log("clicked: %o",  this);
  });

<button id="test">click me</button>

注意:通常,此模式是为加载时不存在的元素保留的,或者避免附加大量的处理程序。同样值得指出的是,虽然我在document上附加了一个处理程序(用于演示目的),但您应该选择最近的可靠祖先。

选项4:defer属性

使用defer属性。

[defer, a Boolean attribute,] is set to indicate to a browser that the script is meant to be executed after the document has been parsed.

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

以下是外部脚本中的代码以供参考:

1
2
3
document.getElementById("test").addEventListener("click", function(e){
   console.log("clicked: %o", this);
});

注意:defer属性看起来确实像是一个魔法子弹,但要注意注意警告是很重要的…1。defer只能用于外部脚本,即那些具有src属性的脚本。2。注意浏览器支持,即:Ie<10中的Buggy实现


简明扼要:因为您要查找的元素尚不存在于文档中。

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

可能的原因

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

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

    顺便说一下,在大多数实现querySelector()querySelectorAll()方法的现代浏览器中,CSS样式的符号用于通过其id检索元素,例如:document.querySelector('#elementID'),而不是通过其iddocument.getElementById('elementID')下检索元素的方法;首先,#character是必需的,在第二种情况下,它将导致元素无法检索。

  • 元素在您调用getElementById时不存在。

  • 后一种情况很常见。浏览器从上到下分析和处理HTML。这意味着,对DOM元素的任何调用(在该DOM元素出现在HTML中之前发生)都将失败。

    请考虑以下示例:

    1
        var element = document.getElementById('my_element');

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

    JQuery

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

    如果找不到jquery,则会出现一个附加的扭曲,因为您已经加载了没有协议的脚本,并且正在从文件系统运行:

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

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

    它有一个不幸的副作用,即试图和未能加载file://somecdn.somewhere.com...

    解决

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

    只需将javascript放在相应的dom元素之后就可以确保这一点。

    1
        var element = document.getElementById('my_element');

    在这种情况下,您还可以将代码放在结束主体标记(之前)(脚本执行时所有DOM元素都可用)。

    其他解决方案包括听取loadDOMContentLoaded事件。在这些情况下,不管您将JavaScript代码放在文档的什么位置,您只需记住将所有DOM处理代码放在事件处理程序中。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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()文档所做的。影响dom元素的所有jquery代码都应该在该事件处理程序中。

    实际上,jquery教程明确指出:

    As almost everything we do when using jQuery reads or manipulates the document object model (DOM), we need to make sure that we start adding events etc. as soon as the DOM is ready.

    To do this, we register a ready event for the document.

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

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

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

    两者都是等效的。


    基于ID的选择器不工作的原因

  • 指定ID为的元素/dom尚不存在。
  • 元素存在,但它没有在DOM中注册[如果HTML节点是从Ajax响应动态附加的]。
  • 存在多个具有相同ID的元素,这导致冲突。
  • 解决

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

  • 对于来自Ajax响应的元素,使用jquery的.bind()方法。旧版本的jquery也有相同的.live()

  • 使用工具[例如,浏览器的WebDeveloper插件]查找重复的ID并删除它们。


  • 正如@felixkling指出的,最有可能的情况是您要查找的节点不存在(还没有)。

    然而,现代开发实践通常可以在文档树之外使用文档片段或直接分离/重新附加当前元素来操作文档元素。这些技术可以作为JavaScript模板化的一部分使用,或者在相关元素发生重大更改时避免过度的重新绘制/回流操作。

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

    不过,同样,您要查找的元素很可能不在文档中,您应该按照Felix的建议进行操作。但是,您也应该知道,这越来越不是元素不可驻(临时或永久)的唯一原因。


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

    如果您想在iframe中获得一个元素,您可以在这里找到方法。


    尝试将document.getElementById放入setTimeout()中。

    例如。

    1
    2
    3
    setTimeout(function(){
        console.log(document.getElementById('whatever'));
    }, 100);

    如果这可行,那么这只是一个时间问题。