为什么jQuery或诸如getElementById之类的DOM方法找不到元素?


483

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

问题示例包括:

  • jQuery默默地未能绑定事件处理程序
  • jQuery的“吸气”方法(.val().html().text())返回undefined
  • 返回标准DOM方法会null导致以下几种错误:

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

最常见的形式是:

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

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

未捕获的TypeError:无法读取null的属性“样式”


32
为什么会找不到一个特定的DOM元素,很多问题被问到,原因通常是因为JavaScript代码位于DOM元素之前。这旨在作为此类问题的规范答案。这是社区Wiki,因此请随时进行改进
Felix Kling 2012年

Answers:


481

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

依赖DOM的脚本的位置可能对其行为产生深远的影响。浏览器从上到下解析HTML文档。元素被添加到DOM中,并且脚本(通常)在遇到脚本时执行。这意味着顺序很重要。通常,脚本无法找到出现在标记后面的元素,因为这些元素尚未添加到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标记之前,将脚本进一步移至页面下方。以这种方式组织的文档的其余部分在执行脚本之前已被解析:

<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被完全解析为止:$(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: %o", this);
    });
  });
</script>
<button id="test">click me</button>

注意:您可以简单地绑定到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>

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


选项4:defer属性

使用的defer属性<script>

[ defer,一个Boolean属性,]设置为向浏览器指示脚本应在文档解析后但在fire之前执行DOMContentLoaded

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

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

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

注意:该defer属性看起来确实像是魔术子弹,但请注意以下几点很重要...
1. defer仅可用于外部脚本,即:具有src属性的脚本。
2.注意浏览器支持,即:IE <10中的错误实现


现在是2020年-“将脚本放置在底部”是否仍“被认为是最佳实践”?我(今天)将所有资源都放在脚本中<head>defer在脚本上使用(我不必支持老式的不兼容浏览器)
Stephen P

我大力支持使用现代浏览器。就是说,这种做法并没有花我什么钱,它随处可见。在另一方面,无论是deferasync具有相当广泛的支持-甚至可以追溯到一些更老版本的浏览器。它们还有一个明显的优点,即明确,明确地表示预期的行为。请记住,这些属性仅适用于指定src属性的脚本,即:外部脚本。权衡收益。也许两者都用?您的来电。
佳能

146

简短:由于您要查找的元素在文档中不存在(尚未)。


对于此答案的其余部分我将使用getElementById作为例子,但是同样适用于getElementsByTagNamequerySelector和任何其它DOM方法,其选择的元件。

可能的原因

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

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

    顺便提及,在大多数实现querySelector()querySelectorAll()方法的当代浏览器中,CSS样式表示法用于通过其元素来检索元素id,例如:document.querySelector('#elementID')与通过其idunder 来检索元素的方法相反document.getElementById('elementID');在第一个中,#字符是必不可少的,在第二个中,这将导致无法检索元素。

  2. 该元素不存在,此刻你打电话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...


解决方案

在调用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:https:方案,调整路径等)。

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

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

正如使用jQuery时所做的几乎所有操作都会读取或操作文档对象模型(DOM)一样,我们需要确保在DOM准备就绪后立即开始添加事件等。

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

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

另外,您也可以使用简写语法:

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

两者是等效的。


1
值得对ready事件的最后一部分进行修改以反映“较新的”首选语法,还是将其保持原样以使其能在较旧的版本中使用更好?
Tieson T.

1
jQuery的,是否值得还增加在替代$(window).load()(也可能是语法替代的$(document).ready()$(function(){})它似乎有关,但它?感觉稍微切到时候,你正在做。
大卫说起用莫妮卡

@David:很好.load。关于语法替代,可以在文档中查找它们,但是由于答案应该是独立的,因此可能值得添加它们。再说一次,我很确定这个关于jQuery的特定问题已经得到了回答,并且可能在其他答案中也可以找到,并且链接到它就足够了。
Felix Kling 2012年

1
@David:我必须修改:显然.load已弃用:api.jquery.com/load-event。我不知道它是否仅用于图像window
Felix Kling 2012年

1
@David:不用担心:)即使您不确定,也可以提到它。我可能只会添加一些简单的代码片段来显示用法。感谢您的输入!
Felix Kling 2012年

15

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

  1. 指定ID的元素/ DOM尚不存在。
  2. 该元素存在,但未在DOM中注册(对于从Ajax响应动态追加的HTML节点而言)。
  3. 存在多个具有相同ID的元素,这会导致冲突。

解决方案

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

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

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


13

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

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

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

同样,尽管如此,很可能您正在寻找的元素还没有在文档中(尚未),您应该按照Felix的建议进行操作。但是,您还应该意识到,这不再是元素可能无法找到(临时或永久)的唯一原因。



7

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

  • 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 #直接放在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>

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

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

    <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等等,几乎可以肯定是行不通的。


0

当我尝试您的代码时,它起作用了。

您的事件无法正常运行的唯一原因可能是您的DOM尚未准备就绪,并且ID为“ event-btn”的按钮尚未准备就绪。并且您的JavaScript已执行,并尝试将事件与该元素绑定。

在使用DOM元素进行绑定之前,应准备好该元素。有很多选择可以做到这一点。

选项1:您可以在文档就绪事件中移动事件绑定代码。喜欢:

document.addEventListener('DOMContentLoaded', (event) => {
  //your code to bind the event
});

选项2:您可以使用超时事件,以便绑定延迟几秒钟。喜欢:

setTimeout(function(){
  //your code to bind the event
}, 500);

选项3:将您的javascript包含移动到页面底部。

我希望这可以帮助你。


@ Willdomybest18,请检查该答案
Jatinder Kumar

-1

问题是执行javascript代码时未创建dom元素“ speclist”。因此,我将javascript代码放入一个函数中,并在body onload事件中调用了该函数。

function do_this_first(){
   //appending code
}

<body onload="do_this_first()">
</body>

-1

我的解决方案是不使用锚元素的ID :<a id='star_wars'>Place to jump to</a>。显然,blazor和其他spa框架的问题跳到了同一页面上。为了避开我不得不使用的地方document.getElementById('star_wars')。但是,直到我将id放在一个段落元素中之前,这才起作用<p id='star_wars'>Some paragraph<p>

使用引导程序的示例:

<button class="btn btn-link" onclick="document.getElementById('star_wars').scrollIntoView({behavior:'smooth'})">Star Wars</button>

... lots of other text

<p id="star_wars">Star Wars is an American epic...</p>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.