首页 文章

为什么jQuery或像getElementById这样的DOM方法找不到元素?

提问于
浏览
410

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

示例问题包括:

  • jQuery默默地无法绑定事件处理程序

  • jQuery "getter"方法( .val().html().text() )返回 undefined

  • 返回 null 的标准DOM方法导致以下任何错误:

未捕获的TypeError:无法设置null的属性'...'未捕获TypeError:无法读取null的属性'...'

最常见的形式是:

未捕获的TypeError:无法设置null的属性'onclick'未捕获TypeError:无法读取null的属性'addEventListener'未捕获的TypeError:无法读取null的属性'style'

6 回答

  • 135

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

    DOM依赖脚本的位置会对其行为产生深远的影响 . 浏览器从上到下解析HTML文档 . 元素被添加到DOM中,脚本(通常)在遇到它们时执行 . This means that order matters. 通常,脚本无法找到稍后出现在标记中的元素,因为这些元素尚未添加到DOM中 .

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

    <script>
      console.log("script #1: %o", document.getElementById("test")); // null
    </script>
    <div id="test">test div</div>
    <script>
      console.log("script #2: %o", 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: %o", this);
        });
      </script>
    </body><!-- closing body tag -->
    

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


    选项2:jQuery就绪()

    使用ready()推迟脚本,直到完全解析DOM为止:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
      $(document).ready(function() {
        $("#test").click(function() {
          console.log("clicked: %o", this);
        });
      });
    </script>
    <button id="test">click me</button>
    

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


    选项3:事件委托

    委派事件的优点是,它们可以处理来自稍后添加到文档的后代元素的事件 .

    当一个元素引发一个事件时(假设它是bubbling事件而没有任何东西停止它的传播),该元素的祖先中的每个父元素也会接收该事件 . 这允许我们将一个处理程序附加到现有元素,并在事件从它的后代冒泡时对它们进行冒泡...甚至是在附加处理程序之后添加的事件 . 我们所要做的就是检查事件是否由所需元素引发,如果是,则运行我们的代码 .

    jQuery的on()为我们执行了这个逻辑 . 我们只提供一个事件名称,所需后代的选择器和一个事件处理程序:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
      $(document).on("click", "#test", function(e) {
        console.log("clicked: %o",  this);
      });
    </script>
    <button id="test">click me</button>
    

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


    选项4:延迟属性

    使用 <script>defer属性 .

    [defer,一个布尔属性]设置为向浏览器指示在解析文档后要执行脚本 .

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

    作为参考,这是来自external script的代码:

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

    注意:defer属性当然看起来像一个神奇的子弹,但重要的是要注意注意事项...... 1.延迟只能用于外部脚本,即:具有src属性的脚本 . 2.注意浏览器支持,即:IE <10中的错误实现

  • 10

    Short and simple: 因为您要查找的元素(尚未)存在于文档中 .


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

    Possible Reasons

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

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

    顺便提一下,在实现 querySelector()querySelectorAll() 方法的majority of contemporary browsers中,CSS样式表示法用于通过 id 检索元素,例如: document.querySelector('#elementID') ,而不是在 document.getElementById('elementID') 下由 id 检索元素的方法;在第一个字符中, # 字符是必不可少的,在第二个字符中,它会导致元素无法被检索 .

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

    后一种情况很常见 . 浏览器从上到下解析和处理HTML . 这意味着在该DOM元素出现在HTML之前对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... 的不幸副作用


    Solutions

    在调用 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
    });
    

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

    jQuery

    首先确保正确加载jQuery . Use the browser's developer tools以查明是否找到了jQuery文件并更正了URL(例如,在开头添加 http:https: 方案,调整路径等)

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

    事实上,jQuery tutorial明确指出:

    由于我们在使用jQuery时几乎所有操作都会读取或操作文档对象模型(DOM),因此我们需要确保在DOM准备就绪后立即开始添加事件等 . 为此,我们为文档注册一个ready事件 . $(document).ready(function(){
    //在DOM准备好时做一些事情
    });

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

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

    两者都是等价的 .

  • 419

    Reasons why id based selectors don't work

    • 指定了id的元素/ DOM尚不存在 .

    • 元素存在,但它没有在DOM中注册[如果从Ajax响应动态附加HTML节点] .

    • 存在多个具有相同ID的元素,这会导致冲突 .

    Solutions

    • 尝试在声明后访问元素,或者使用像 $(document).ready(); 这样的东西

    • 对于来自Ajax响应的元素,请使用jQuery的 .bind() 方法 . 较旧版本的jQuery也有 .live() .

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

  • 13

    正如@FelixKling指出的那样,最可能的情况是您正在寻找的节点(尚未存在) .

    但是,现代开发实践通常可以使用DocumentFragments操作文档树之外的文档元素,或者直接分离/重新附加当前元素 . 这些技术可以用作JavaScript模板的一部分,或者在所讨论的元素被大量改变时避免过多的重绘/重排操作 .

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

    但是,再一次,很可能你正在寻找的元素不是(还)在文档中,你应该按照Felix的建议去做 . 但是,你也应该如此意识到这一点越来越不是元素可能无法实现(临时或永久)的唯一原因 .

  • -4

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

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

  • 11

    尝试将 document.getElementById 放入 setTimeout()

    例如 .

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

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

相关问题