如果我将脚本元素放在页面顶部附近,为什么 jQuery 或 DOM 方法(例如 getElementById)找不到该元素?
document.getElementById
、
$("#id")
或任何其他 DOM 方法 / jQuery 选择器未找到元素的可能原因是什么?
示例问题包括:
- jQuery 默默地无法绑定事件处理程序
-
jQuery“getter”方法(
.val()
、.html()
、.text()
)返回undefined
-
标准 DOM 方法返回
null
导致以下任一错误:
Uncaught TypeError: Cannot set property '...' of null
Uncaught TypeError: Cannot set properties of null (setting '...')
Uncaught TypeError: Cannot read property '...' of null
Uncaught TypeError: Cannot read properties of null (reading '...')
最常见的形式是:
Uncaught TypeError: Cannot set property 'onclick' of null
Uncaught TypeError: Cannot read property 'addEventListener' of null
Uncaught TypeError: Cannot read property 'style' of null
脚本运行时,您尝试查找的元素不在 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:移动您的脚本
<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
, a Boolean attribute,] is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firingDOMContentLoaded
.
这意味着您可以将标有
defer
的脚本放置在任何地方,甚至是
<head>
,并且它应该可以访问完全实现的 DOM。
<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>
Window:load 事件
load
事件在
DOMContentLoaded
和其他资源(如样式表和图像)加载后触发。因此,它触发的时间比我们预期的要晚。不过,如果您考虑使用 IE8 等较旧的浏览器,则支持几乎是通用的。当然,您可能需要
addEventListener()
的 polyfill。
<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
和
window:load
各有其注意事项。 jQuery 的
ready()
提供了一种混合解决方案,在可能的情况下使用
DOMContentLoaded
,在必要时故障转移到
window:load
,如果 DOM 已完成,则立即触发其回调。
您可以将 ready 处理程序作为
$(
handler
)
直接传递给 jQuery,例如:
<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>
简短而简单: 因为您要查找的元素在文档中尚不存在。
对于此答案的其余部分,我将使用
getElementById
作为示例,但这同样适用于
getElementsByTagName
、
querySelector
以及任何其他选择的 DOM 方法元素。
可能的原因
元素可能不存在的原因有三个:
-
具有传递的 ID 的元素实际上并不存在于文档中。您应该仔细检查传递给
getElementById
的 ID 是否确实与(生成的)HTML 中现有元素的 ID 匹配,并且您没有 拼错 ID(ID 区分大小写!)。如果您使用
getElementById
,请确保您 仅 提供元素的 ID(例如,document.getElemntById("the-id")
)。如果您使用接受 CSS 选择器的方法(如querySelector
),请确保在 ID 前包含#
,以表明您正在查找 ID(例如document.querySelector("#the-id")
)。您 不得 将#
与getElementById
一起使用,并且 必须 将其与querySelector
及类似方法一起使用。另请注意,如果 ID 中包含在 CSS 标识符 中无效的字符(例如.
;id
属性包含.
字符的做法不太好,但确实有效),则必须在使用querySelector
(document.querySelector("#the\\.id")
)) 时转义这些字符,但在使用getElementById
(document.getElementById("the.id")
) 时无需转义。 -
调用
getElementById
时, 该元素 不存在。 -
即使元素不在您正在查询的文档中,您可以在页面上看到它,因为它位于
iframe
中(它是它自己的文档)。当您搜索包含它们的文档时,不会搜索iframes
中的元素。
如果问题是原因 3(它位于
iframe
或类似元素中),则需要查看
iframe
中的文档,而不是父文档,也许可以通过获取
iframe
元素并使用其
contentDocument
属性来访问其文档(仅限同源)。本答案的其余部分解决了前两个原因。
第二个原因 - 不存在 - 很普遍。浏览器分析并处理HTML从上到下。这意味着任何对DOM元素出现在HTML中之前发生的DOM元素的调用,将失败。
考虑以下示例:
438606071
div
在
脚本
之后出现
。目前执行脚本,该元素尚不存在
和
getElementById
将返回
null
。
jQuery
使用jQuery的所有选择器都适用。如果您 拼写错误 您的选择器,或者您正在尝试选择它们 ,则不会找到 。 。之所以找到,是因为您已经没有协议加载了脚本并从文件系统运行:
880061579
此语法用于允许脚本通过协议在页面上通过HTTPS加载https://并使用协议上加载http版本http://
它具有不幸的副作用,即尝试和不加载
file> file> file> file> file> file> file> file>://somecdn.somewhere.com ...
solutions
在您致电
getElementById
之前DOM方法),请确保您要访问的元素存在,即加载DOM。
可以通过简单地将JavaScript 放置在 之后来确保此相应的DOM element
164229616
在这种情况下,您还可以在关闭的主体标签(
&lt;/body&gt;
)之前放置代码(所有DOM元素都将在执行脚本时可用)。
其他解决方案包括收听
load
[mdn]
或
domcontentloaded
[mdn] >事件。在这些情况下,在文档中放置JavaScript代码并不重要,您只需要记住将所有DOM处理代码放在事件处理程序中。
示例:
88888239631
请参阅 文章在quirksmode.org上进行文章事件处理和浏览器差异。
jQuery
首先确保正确加载jQuery。
使用浏览器的开发人员工具URL如果不是(例如,添加
http:
或
https:
ofent在开始时的方案,调整路径等)
load
/
domcontentloaded
事件正是JQuery所做的
.ready()
[docs]
。影响DOM元素的所有jQuery代码都应在该事件处理程序内部。
事实上, jQuery 教程 明确指出:
As almost everything we do when using jQuery reads or manipulates the document object model (DOM), we need to make sure that we start adding events etc. as soon as the DOM is ready.
To do this, we register a ready event for the document.
$(document).ready(function() {
// do stuff when DOM is ready
});
或者,您也可以使用简写语法:
$(function() {
// do stuff when DOM is ready
});
两者是等效的。
基于 id 的选择器不起作用的原因
- 指定 id 的元素/DOM 尚不存在。
- 元素存在,但未在 DOM 中注册(在 HTML 节点从 Ajax 响应动态附加的情况下)。
- 存在多个具有相同 id 的元素,这会导致冲突。
解决方案
-
尝试在声明元素后访问元素,或者使用
$(document).ready();
之类的东西
-
对于来自 Ajax 响应的元素,请使用 jQuery 的
.bind()
方法。旧版本的 jQuery 有.live()
。 -
使用工具 [例如,浏览器的 webdeveloper 插件] 查找重复的 ID 并将其删除。