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

Fel*_*ing 446 javascript jquery dom

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

示例问题包括:

  • jQuery默默地无法绑定事件处理程序
  • jQuery的"吸气"方法(.val(),.html(),.text())返回undefined
  • 返回的标准DOM方法null导致以下任何错误:

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

    最常见的形式是:

    未捕获的TypeError:无法设置null的属性'onclick'

    未捕获的TypeError:无法读取null的属性"addEventListener"

    未捕获的TypeError:无法读取null的属性'style'

I a*_*ica 452

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

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

考虑以下标记; 脚本#1无法找到<div>while脚本#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>
Run Code Online (Sandbox Code Playgroud)

那你该怎么办?你有几个选择:


选项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 -->
Run Code Online (Sandbox Code Playgroud)

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


选项2:jQuery ready()

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

<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>
Run Code Online (Sandbox Code Playgroud)

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


选项3:事件委派

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

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

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>
Run Code Online (Sandbox Code Playgroud)

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


选项4:defer属性

使用的defer属性<script>.

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

<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>
Run Code Online (Sandbox Code Playgroud)

作为参考,这是来自该外部脚本的代码:

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

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

  • “defer”属性对我帮助很大。此外,这可能是我在这里见过的最详细的答案之一。 (4认同)
  • 现在已经是 2020 年了——“将脚本放在底部”仍然“被认为是最佳实践”吗?我(现在)将所有资源放在“&lt;head&gt;”中,并在脚本上使用“defer”(我不必支持旧的不兼容的浏览器) (3认同)

Fel*_*ing 143

简短:因为您正在寻找的元素(尚未)存在于文档中.


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

可能的原因

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

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

    顺便提及,在大多数当代的浏览器,其实现querySelector()querySelectorAll()方法,CSS样式符号用于检索由它的元件id,例如:document.querySelector('#elementID'),而不是通过该元件通过其检索的方法iddocument.getElementById('elementID'); 在第一个#字符是必不可少的,在第二个字符中它将导致元素不被检索.

  2. 该元素不存在,此刻你打电话getElementById.

后一种情况很常见.浏览器从上到下解析和处理HTML.这意味着在该DOM元素出现在HTML之前对DOM元素的任何调用都将失败.

请考虑以下示例:

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

<div id="my_element"></div>
Run Code Online (Sandbox Code Playgroud)

div出现script.在执行脚本的那一刻,元素不存在尚未getElementById返回null.

jQuery的

这同样适用于使用jQuery的所有选择器.如果拼错了选择器,或者在实际存在之前尝试选择它们, jQuery将找不到元素.

一个额外的转折是找不到jQuery,因为你已经加载了没有协议的脚本并且正在从文件系统运行:

<script src="//somecdn.somewhere.com/jquery.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

此语法用于允许脚本通过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>
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您也可以将代码放在结束体标记(</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
});
Run Code Online (Sandbox Code Playgroud)

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

jQuery的

首先确保正确加载jQuery.使用浏览器的开发人员工具查找是否找到了jQuery文件,如果不是,则更正URL(例如,在开头添加http:https:方案,调整路径等)

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

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

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

为此,我们为文档注册一个ready事件.

$(document).ready(function() {
   // do stuff when DOM is ready
});
Run Code Online (Sandbox Code Playgroud)

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

$(function() {
    // do stuff when DOM is ready
});
Run Code Online (Sandbox Code Playgroud)

两者都是等价的.


sum*_*mit 14

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

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

解决方案

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

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

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


Nat*_*bna 12

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

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

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

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


Geo*_*gan 11

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

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


Cer*_*nce 11

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

  • getElementById要求传递的字符串是 ID逐字,没有别的。如果您在传递的字符串前面加上 a #,并且 ID 不以 a 开头#,则不会选择任何内容:

      <div id="foo"></div>
    
    Run Code Online (Sandbox Code Playgroud)
      // Error, selected element will be null:
      document.getElementById('#foo')
      // Fix:
      document.getElementById('foo')
    
    Run Code Online (Sandbox Code Playgroud)
  • 同样,对于getElementsByClassName,不要在传递的字符串前加上前缀.

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

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

    在大多数情况下,这里的规则与 CSS 选择器的规则相同,可以在此处查看详细信息

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

      <div class="foo bar"></div>
    
    Run Code Online (Sandbox Code Playgroud)

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

      <div class="foo" data-bar="someData"></div>
    
    Run Code Online (Sandbox Code Playgroud)

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

      <div class="parent">
        <span data-username="bob"></span>
      </div>
    
    Run Code Online (Sandbox Code Playgroud)

    使用div.parent > span[data-username="bob"].

  • 资本和拼写事情做上述所有的。如果大小写不同,或者拼写不同,元素将不会被选中:

      <div class="result"></div>
    
    Run Code Online (Sandbox Code Playgroud)
      // Error, selected element will be null:
      document.querySelector('.results')
      $('.Result')
      // Fix:
      document.querySelector('.result')
      $('.result')
    
    Run Code Online (Sandbox Code Playgroud)
  • 您还需要确保这些方法具有正确的大小写和拼写。使用以下之一:

    $(selector)
    document.querySelector
    document.querySelectorAll
    document.getElementsByClassName
    document.getElementsByTagName
    document.getElementById
    
    Run Code Online (Sandbox Code Playgroud)

    任何其他拼写或大写都不起作用。例如,document.getElementByClassName会抛出错误。

  • 确保将字符串传递给这些选择器方法。如果传递的东西是不是一个字符串querySelectorgetElementById等等,几乎可以肯定是行不通的。

  • 如果要选择的元素的 HTML 属性被引号包围,则它们必须是纯直引号(单引号或双引号);如果您尝试按 ID、类或属性进行选择,则像或这样的卷曲引号将不起作用。