为什么 jQuery 或 getElementById 等 DOM 方法找不到该元素?
- 2024-11-02 21:00:00
- admin 原创
- 40
问题描述:
document.getElementById
或者$("#id")
任何其他 DOM 方法/ jQuery 选择器找不到元素的可能原因是什么?
示例问题包括:
jQuery 静默地无法绑定事件处理程序
jQuery “getter” 方法(
.val()
,.html()
,.text()
)返回undefined
标准 DOM 方法返回
null
导致以下任一错误:
未捕获的 TypeError:无法设置 null 的属性 '...'
未捕获的 TypeError:无法设置 null 的属性(设置 '...')
未捕获的 TypeError:无法读取 null 的属性 '...'
未捕获的 TypeError:无法读取 null 的属性(读取 '...')
最常见的形式是:
未捕获的类型错误:无法设置值为 null 的属性“onclick”
未捕获的类型错误:无法读取值为 null 的属性“addEventListener”
未捕获的类型错误:无法读取值为 null 的属性“style”
解决方案 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>
运行代码片段Hide results展开片段
那么,你应该怎么做呢?你有几个选择:
选项 1:移动脚本
鉴于我们在上例中看到的情况,一个直观的解决方案可能是将脚本移到标记下方,越过您想要访问的元素。事实上,长期以来,出于各种原因,
将脚本放在页面底部被认为是最佳做法。以这种方式组织,文档的其余部分将在执行脚本之前进行解析:
<body>
<button id="test">click me</button>
<script>
document.getElementById("test").addEventListener("click", function() {
console.log("clicked:", this);
});
</script>
</body><!-- closing body tag -->
运行代码片段Hide results展开片段
虽然这很有意义,并且对于传统浏览器来说是一个可靠的选择,但它是有限的,而且还有更灵活、更现代的方法可用。
选项 2:defer
属性
虽然我们确实说过脚本“(默认情况下)在遇到时执行”,但现代浏览器允许您指定不同的行为。如果您要链接外部脚本,则可以使用该defer
属性。
[
defer
,一个布尔属性,] 被设置为向浏览器指示该脚本将在文档解析之后、但在触发之前执行DOMContentLoaded
。
这意味着您可以将带有标记的脚本放置defer
在任何地方,甚至是<head>
,并且它应该可以访问完全实现的 DOM。
<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>
运行代码片段Hide results展开片段
只要记住...
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>
运行代码片段Hide results展开片段
选项 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>
运行代码片段Hide results展开片段
窗口:加载事件
该load
事件在加载样式表和图像等其他资源后触发DOMContentLoaded
。因此,它触发的时间比我们预期的要晚。不过,如果您考虑使用 IE8 等较旧的浏览器,则支持几乎是通用的。当然,您可能需要一个polyfilladdEventListener()
。
<script>
window.addEventListener("load", function(e){
document.getElementById("test").addEventListener("click", function(e) {
console.log("clicked:", this);
});
});
</script>
<button id="test">click me</button>
运行代码片段Hide results展开片段
jQuery 的ready()
DOMContentLoaded
并且window:load
每种方法都有其注意事项。jQueryready()
提供了一种混合解决方案,DOMContentLoaded
在可能时使用,在必要时进行故障转移window:load
,并且如果 DOM 已完成则立即触发其回调。
您可以将就绪处理程序直接传递给 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>
运行代码片段Hide results展开片段
选项 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>
运行代码片段Hide results展开片段
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>
运行代码片段Hide results展开片段
解决方案 2:
简短而简单:因为您正在寻找的元素在文档中尚不存在。
对于这个答案的其余部分,我将使用它getElementById
作为示例,但同样适用于getElementsByTagName
、querySelector
和任何其他选择元素的 DOM 方法。
可能的原因
元素可能不存在的原因有以下三个:
具有传递的 ID 的元素实际上并不存在于文档中。您应该仔细检查传递的 ID
getElementById
是否与(生成的)HTML 中现有元素的 ID 相匹配,以及您是否拼错了ID(ID 区分大小写!)。
如果您使用getElementById
,请确保您只提供元素的 ID(例如document.getElemntById("the-id")
)。如果您使用接受 CSS 选择器的方法(如querySelector
),请确保在#
ID 之前包含 以表明您正在寻找 ID(例如document.querySelector("#the-id")
)。您不能将#
和 一起使用getElementById
,而必须将其和 以及类似符号一起使用。另请注意,如果 ID 中包含在CSS 标识符querySelector
中无效的字符(例如;包含字符的属性是不好的做法,但有效),则必须在使用( ))时转义这些字符,但在使用( ) 时则无需转义。.
`id.
querySelectordocument.querySelector("#the\.id")
getElementById`document.getElementById("the.id")
当您调用时,该元素不存在
getElementById
。尽管您可以在页面上看到该元素,但它并不在您查询的文档中,因为它位于
iframe
(它自己的文档)中。iframes
当您搜索包含这些元素的文档时,不会搜索 中的元素。
如果问题是原因 3(它位于iframe
或类似文件中),则需要查看 中的文档iframe
,而不是父文档,也许可以通过获取iframe
元素并使用其contentDocument
属性来访问其文档(仅限同源)。本答案的其余部分解决了前两个原因。
第二个原因(尚未实现 )很常见。浏览器从上到下解析和处理 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 标签(</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 准备就绪后立即开始添加事件等。
为此,我们为文档注册了一个就绪事件。
$(document).ready(function() {
// do stuff when DOM is ready
});
或者您也可以使用简写语法:
$(function() {
// do stuff when DOM is ready
});
两者是等效的。
解决方案 3:
基于 id 的选择器不起作用的原因
具有指定 id 的元素/DOM 尚不存在。
该元素存在,但未在 DOM 中注册(如果 HTML 节点是从 Ajax 响应动态附加的)。
存在多个具有相同 ID 的元素,从而导致冲突。
解决方案
尝试在声明后访问元素,或者使用类似
$(document).ready();
对于来自 Ajax 响应的元素,请使用
.bind()
jQuery 的方法。旧版本的 jQuery 也有.live()
相同的方法。使用工具[例如,浏览器的 webdeveloper 插件] 查找重复的 ID 并将其删除。
解决方案 4:
如果您尝试访问的元素位于内部,iframe
而您尝试在上下文之外访问它,iframe
这也会导致它失败。
如果您想获取 iframe 中的元素,您可以在这里了解如何获取。
解决方案 5:
正如@FelixKling 指出的那样,最可能的情况是您正在寻找的节点尚不存在。
然而,现代开发实践通常可以使用 DocumentFragments 或直接分离/重新连接当前元素来操作文档树之外的文档元素。此类技术可用作 JavaScript 模板的一部分,或在相关元素发生重大更改时避免过多的重绘/重排操作。
类似地,现代浏览器推出的新“Shadow DOM”功能允许元素成为文档的一部分,但不能通过 document.getElementById 及其所有同级方法(querySelector 等)进行查询。这样做是为了封装功能并专门隐藏它。
不过,最有可能的是,您要查找的元素尚未出现在文档中,您应该按照 Felix 的建议去做。但是,您还应该意识到,这越来越不是元素无法找到(无论是暂时还是永久)的唯一原因。
解决方案 6:
如果脚本执行顺序不是问题,则问题的另一个可能原因是未正确选择元素:
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 前面:
<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、类或属性进行选择,则像
‘
或 这样的花引号”
将不起作用。
解决方案 7:
我使用的是 Apache Tapestry,但就我而言,上述答案均无济于事。最后,我发现错误是由于对“id”和“t:id”使用相同的值而导致的。希望对您有所帮助。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件