为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

Posted

技术标签:

【中文标题】为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?【英文标题】:Why does jQuery or a DOM method such as getElementById not find the element?为什么 jQuery 或 getElementById 等 DOM 方法找不到元素? 【发布时间】:2022-01-18 20:25:41 【问题描述】:

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

示例问题包括:

jQuery 静默绑定事件处理程序失败 jQuery“getter”方法(@​​987654323@、.html().text())返回undefined 返回 null 的标准 DOM 方法会导致多个错误中的任何一个:

未捕获的类型错误:无法设置属性 '...' of null 未捕获的 TypeError:无法设置 null 的属性(设置“...”) 未捕获的类型错误:无法读取 null 的属性“...” 未捕获的 TypeError:无法读取 null 的属性(正在读取 '...')

最常见的形式是:

未捕获的类型错误:无法将属性“onclick”设置为 null 未捕获的类型错误:无法读取 null 的属性“addEventListener” 未捕获的类型错误:无法读取 null 的属性“样式”

【问题讨论】:

很多问题都是关于为什么找不到某个 DOM 元素,原因通常是因为 javascript 代码放在 DOM 元素之前。这旨在成为此类问题的规范答案。这是社区 wiki,所以请随时改进 【参考方案1】:

当您的脚本运行时,您尝试查找的元素不在 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:移动脚本

鉴于我们在上面的示例中看到的情况,一个直观的解决方案可能是简单地将您的脚本移到标记下方,越过您想要访问的元素。事实上,长期以来,出于各种原因,将脚本放在页面底部被认为是best practice。以这种方式组织,将在执行脚本之前解析文档的其余部分:

<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 标记的脚本放置在任何地方,甚至是&lt;head&gt;,并且它应该可以访问完全实现的DOM。

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

请记住...

    defer 只能用于外部脚本,即:具有src 属性的脚本。 注意browser support,即:IE 中的错误实现

选项 3:模块

根据您的要求,您可以使用JavaScript modules。除了与标准脚本 (noted here) 的其他重要区别外,模块会自动延迟,并且不限于外部源。

将脚本的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>

窗口:加载事件

load 事件在DOMContentLoaded 之后触发,并且样式表和图像等其他资源已加载。出于这个原因,它的触发时间比我们预期的要晚。不过,如果您正在考虑使用 IE8 等较旧的浏览器,则支持几乎是普遍的。当然,您可能需要polyfill for 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 的ready()

DOMContentLoadedwindow:load 各有注意事项。 jQuery 的ready() 提供了一种混合解决方案,尽可能使用DOMContentLoaded,必要时故障转移到window:load,如果DOM 已经完成,则立即触发其回调。

您可以将准备好的处理程序以$(<em>handler</em>) 的形式直接传递给 jQuery,例如:

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

选项 5:事件委托

将事件处理委托给目标元素的祖先。

当一个元素引发一个事件时(假设它是一个bubbling 事件并且没有任何东西阻止它的传播),该元素的祖先中的每个父级,一直到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 的on()

jQuery 通过on() 提供此功能。给定事件名称、所需后代的选择器和事件处理程序,它将解析您的委托事件处理并管理您的 this 上下文:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></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>

【讨论】:

defer 属性对我帮助很大。此外,这可能是我在这里见过的最详细的答案之一。【参考方案2】:

简短而简单:因为您要查找的元素在文档中不存在(目前)。


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

可能的原因

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

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

    如果您使用的是getElementById,请确保您提供元素的 ID(例如,document.getElemntById("the-id"))。如果您使用的方法接受 CSS 选择器(例如 querySelector),请确保您在 ID 前包含 # 以表明您正在查找 ID(例如,document.querySelector("#the-id"))。您必须#getElementById 一起使用,并且必须将其与querySelector 和类似的一起使用。另请注意,如果 ID 中包含在 CSS identifiers 中无效的字符(例如 .id 包含 . 字符的属性是不好的做法,但有效),您必须在以下情况下转义这些字符使用querySelector (document.querySelector("#the\\.id"))) 但在使用getElementById (document.getElementById("the.id")) 时不使用。

    元素不存在目前你调用getElementById

    该元素不在您正在查询的文档中,即使您可以在页面上看到它,因为它位于 iframe(它自己的文档)中。 iframes 中的元素在您搜索包含它们的文档时不会被搜索。

如果问题是原因 3(它位于 iframe 或类似名称中),您需要查看 iframe 中的文档,而不是父文档,可能通过获取 iframe 元素并使用其 @ 987654325@ 属性以访问其文档(仅限同源)。这个答案的其余部分解决了前两个原因。

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

考虑以下示例:

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

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

div 出现在script 之后。在执行脚本的那一刻,该元素不存在,getElementById 将返回null

jQuery

这同样适用于所有带有 jQ​​uery 的选择器。如果您拼错了选择器,或者您试图在它们实际存在之前选择它们,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>

在这种情况下,您还可以将代码放在结束正文标记 (&lt;/body&gt;) 之前(在执行脚本时所有 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 准备好后立即开始添加事件等。

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

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

您也可以使用简写语法:

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

两者是等价的。

【讨论】:

【参考方案3】:

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

getElementById 要求传递的字符串是 ID verbatim,仅此而已。如果您在传递的字符串前加上 # 前缀,并且 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 选择器的规则相同,可以详细查看 here。

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

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

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

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

使用查询字符串.foo[data-bar="someData"]。选择下面的&lt;span&gt;

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

使用div.parent &gt; 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 等,它几乎肯定不会起作用。

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

【讨论】:

【参考方案4】:

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

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

解决方案

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

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

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

【讨论】:

【参考方案5】:

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

如果您想在 iframe 中获取元素,您可以了解如何使用 here。

【讨论】:

【参考方案6】:

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

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

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

同样,您要查找的元素很可能(尚未)在文档中,您应该按照 Felix 的建议进行操作。但是,您还应该意识到,这越来越不是元素可能无法找到(暂时或永久)的唯一原因。

【讨论】:

以上是关于为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?

为啥 jQuery 或 getElementById 等 DOM 方法找不到元素?