提问人:Felix Kling 提问时间:12/25/2012 最后编辑:IvarFelix Kling 更新时间:9/21/2023 访问量:195788
为什么 jQuery 或 DOM 方法(如 getElementById)找不到元素?
Why does jQuery or a DOM method such as getElementById not find the element?
问:
或任何其他DOM方法/ jQuery选择器找不到元素的可能原因是什么?document.getElementById
$("#id")
示例问题包括:
- jQuery 以静默方式绑定事件处理程序失败
- jQuery “getter” 方法 (, , ) 返回
.val()
.html()
.text()
undefined
- 返回导致以下任何错误的标准 DOM 方法:
null
未捕获的 TypeError:无法设置属性“...”of null 未捕获的 TypeError:无法设置 null 的属性(设置 '...')
未捕获的 TypeError:无法读取属性“...”of null
未捕获的 TypeError:无法读取 null 的属性(读取“...”)
最常见的形式是:
未捕获的 TypeError:无法将属性“onclick”设置为 null 未捕获的 TypeError:无法读取 null 的属性“addEventListener” 未捕获的 TypeError:无法读取 null
的属性“style”
答:
脚本运行时,您尝试查找的元素不在 DOM 中。
依赖 DOM 的脚本的位置会对其行为产生深远的影响。浏览器从上到下解析 HTML 文档。元素被添加到 DOM 中,脚本(默认情况下)在遇到它们时执行。这意味着顺序很重要。通常,脚本找不到标记中稍后出现的元素,因为这些元素尚未添加到 DOM 中。
请考虑以下标记;脚本 #1 找不到 while 脚本 #2 成功:<div>
<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:移动脚本
鉴于我们在上面的示例中看到的内容,一个直观的解决方案可能是简单地将脚本向下移动,越过要访问的元素。事实上,在很长一段时间里,出于各种原因,将脚本放在页面底部被认为是一种最佳实践。以这种方式组织,文档的其余部分将在执行脚本之前进行解析:
<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
之前执行。
这意味着你可以放置一个标记有 任何地方的脚本,甚至是 ,并且它应该可以访问完全实现的 DOM。defer
<head>
<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>
请记住......
defer
只能用于外部脚本,即:具有属性的脚本。src
- 注意浏览器支持,即:IE < 10 中的错误实现
选项 3:模块
根据您的要求,您可以使用 JavaScript 模块。与标准脚本的其他重要区别(在此处注明)中,模块是自动延迟的,不限于外部源。
将脚本设置为 ,例如: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 事件
load
事件在加载完其他资源(如样式表和图像)后触发。出于这个原因,它的触发时间比我们预期的要晚。不过,如果您正在考虑像 IE8 这样的旧浏览器,那么这种支持几乎是普遍的。当然,您可能需要 addEventListener() 的 polyfill
。DOMContentLoaded
<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()
DOMContentLoaded
每个人都有自己的警告。jQuery 的 ready()
提供了一个混合解决方案,在可能的情况下使用,在必要时故障转移到,并在 DOM 已经完成时立即触发其回调。window:load
DOMContentLoaded
window:load
您可以将准备好的处理程序直接传递给 jQuery,例如:$(handler)
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<script>
$(function() {
$("#test").click(function() {
console.log("clicked:", this);
});
});
</script>
<button id="test">click me</button>
选项 5:事件委派
将事件处理委托给目标元素的上级。
当一个元素引发一个事件时(前提是它是一个冒泡事件,并且没有任何东西阻止它的传播),该元素祖先中的每个父元素(一直到 )也会接收该事件。这允许我们将处理程序附加到现有元素,并在事件从其后代冒出时对其进行采样......甚至来自附加处理程序后添加的后代。我们所要做的就是检查事件,看看它是否是由所需的元素引发的,如果是,则运行我们的代码。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://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></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
简短明了:因为您要查找的元素在文档中尚不存在。
对于本答案的其余部分,我将使用 getElementById
作为示例,但这同样适用于 getElementsByTagName
、querySelector
和任何其他选择元素的 DOM 方法。
可能的原因
元素可能不存在的原因有三个:
具有传递 ID 的元素实际上并不存在在文档中。您应该仔细检查您传递的 ID 是否与(生成的)HTML 中现有元素的 ID 真正匹配,并且您没有拼错 ID(ID 区分大小写!
getElementById
如果您使用的是 ,请确保只提供元素的 ID(例如,)。如果您使用接受 CSS 选择器的方法(例如),请确保在 ID 之前包含 ,以指示您正在寻找 ID(例如,)。不得将 with 一起使用,并且必须将其与 和 similar 一起使用。另请注意,如果 ID 中包含在 CSS 标识符中无效的字符(例如 ; 包含字符的属性是不好的做法,但有效),在使用 ()) 时必须转义这些属性,但在使用 () 时则不然。
getElementById
document.getElemntById("the-id")
querySelector
#
document.querySelector("#the-id")
#
getElementById
querySelector
.
id
.
querySelector
document.querySelector("#the\\.id")
getElementById
document.getElementById("the.id")
调用 时,该元素不存在。
getElementById
该元素不在你正在查询的文档中,即使你可以在页面上看到它,因为它位于一个(这是它自己的文档)中。当您搜索包含元素的文档时,不会搜索这些元素。
iframe
iframes
如果问题是原因 3(它在 an 或类似中),则需要在 而不是父文档中查看文档,也许可以通过获取元素并使用其 contentDocument
属性访问其文档(仅限同源)。这个答案的其余部分解决了前两个原因。iframe
iframe
iframe
第二个原因——它还没有出现——很常见。浏览器从上到下解析和处理 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...
解决 方案
在你调用(或任何与此相关的 DOM 方法)之前,请确保你想要访问的元素存在,即 DOM 已加载。getElementById
这可以通过简单地将 JavaScript 放在相应的 DOM 元素之后来确保
<div id="my_element"></div>
<script>
var element = document.getElementById('my_element');
</script>
在这种情况下,您也可以将代码放在结束 body 标记 () 之前(所有 DOM 元素在执行脚本时都可用)。</body>
其他解决方案包括侦听加载
[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(例如,在开头添加or方案,调整路径等)。http:
https:
监听 / 事件正是 jQuery 使用 .ready()
[docs] 所做的。所有影响 DOM 元素的 jQuery 代码都应该在该事件处理程序中。load
DOMContentLoaded
事实上,jQuery教程明确指出:
由于我们在使用jQuery时所做的几乎所有事情都是读取或操作文档对象模型(DOM),因此我们需要确保在DOM准备就绪后立即开始添加事件等。
为此,我们为文档注册一个就绪事件。
$(document).ready(function() {
// do stuff when DOM is ready
});
或者,您也可以使用速记语法:
$(function() {
// do stuff when DOM is ready
});
两者都是等效的。
正如@FelixKling所指出的,最有可能的情况是你正在寻找的节点(还不存在)。
但是,现代开发实践通常可以使用 DocumentFragments 或直接分离/重新附加当前元素来操作文档树之外的文档元素。这些技术可以用作 JavaScript 模板的一部分,或者在相关元素被大量更改时避免过多的重绘/重排操作。
类似地,在现代浏览器中推出的新的“Shadow DOM”功能允许元素成为文档的一部分,但不能通过document.getElementById及其所有同级方法(querySelector等)进行查询。这样做是为了封装功能并专门隐藏它。
不过,同样,您要查找的元素很可能根本不在文档中,您应该按照 Felix 的建议进行操作。但是,您还应该意识到,这越来越不是元素可能(暂时或永久)无法找到的唯一原因。
基于 id 的选择器不起作用的原因
- 指定了 id 的元素/DOM 尚不存在。
- 该元素存在,但未在 DOM 中注册 [如果从 Ajax 响应动态附加 HTML 节点]。
- 存在多个具有相同 ID 的元素,这会导致冲突。
解决 方案
尝试在元素声明后访问该元素,或者使用类似的东西
$(document).ready();
对于来自 Ajax 响应的元素,请使用 jQuery 方法。旧版本的jQuery也有同样的功能。
.bind()
.live()
使用工具[例如,浏览器的 webdeveloper 插件]查找重复的 ID 并将其删除。
如果您尝试访问的元素位于 内部,并且您尝试在上下文之外访问它,这也将导致其失败。iframe
iframe
如果你想在iframe中获取一个元素,你可以在这里找到方法。
如果脚本执行顺序不是问题,则问题的另一个可能原因是未正确选择元素:
getElementById
要求传递的字符串是逐字的 ID,而不是其他任何内容。如果在传递的字符串前面加上 ,并且 ID 不以 a 开头,则不会选择任何内容:#
#
<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,要将元素与特定类名匹配,请直接在类之前放置 a。同样,要将元素与特定 ID 匹配,请直接在 ID 之前放置 a:
.
#
<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
请确保将字符串传递给这些选择器方法。如果你把不是字符串的东西传递给 , , 等,它几乎肯定是行不通的。
querySelector
getElementById
如果要选择的元素的 HTML 属性被引号括起来,则它们必须是普通的直引号(单引号或双引号);如果您尝试按 ID、类或属性进行选择,则大括号 like 或将不起作用。
‘
”
我正在使用 Apache Tapestry,就我而言,上述答案都没有帮助。最后,我发现错误是由对“id”和“t:id”使用相同的值引起的。 我希望它有所帮助。
上一个:传单滑块 - 自定义外观
评论