当脚本运行时,您试图查找的元素不在DOM中。
依赖于dom的脚本的位置会对其行为产生深远的影响。浏览器从上到下解析HTML文档。元素被添加到DOM中,脚本(通常)在遇到它们时执行。这意味着顺序很重要。通常,脚本无法找到稍后出现在标记中的元素,因为这些元素还没有添加到DOM中。
考虑下面的标记;脚本#1无法找到<div>,而脚本#2成功:
<脚本>
console.log("脚本#1:",document.getElementById("test"));/ /空
> < /脚本
<div id="test">test div</div>
<脚本>
console.log("脚本#2:",document.getElementById("test"));// <div id="test"…
> < /脚本
那么,你应该怎么做呢?你有几个选择:
选项1:移动脚本
Given what we've seen in the example above, an intuitive solution might be to simply move your script down the markup, past the elements you'd like to access. In fact, for a long time, placing scripts at the bottom of the page was considered a best practice for a variety of reasons. Organized in this fashion, the rest of the document would be parsed before executing your script:
<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的脚本放置在任何地方,甚至是<head>,并且它应该可以访问完全实现的DOM。
<script src=“https://gh-canon.github.io/misc-demos/log-test-click.js” defer></script>
<按钮 id=“测试”>单击我</button>
只要记住…
Defer只能用于外部脚本,即:那些具有SRC属性的脚本。
注意浏览器的支持,例如:IE中有bug的实现< 10
选项3:模块
根据您的需求,您可以使用JavaScript模块。在与标准脚本的其他重要区别中(这里提到了),模块是自动延迟的,并且不限于外部源。
将脚本的类型设置为模块,例如:
<脚本类型=“模块”>
. getelementbyid(“测试”)。addEventListener(“点击”,函数(e) {
Console.log("点击:",这个);
});
> < /脚本
<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(“点击:”,这个);
});
});
</script>
<按钮 id=“测试”>单击我</button>
窗口:加载事件
加载事件在加载DOMContentLoaded和其他资源(如样式表和图像)之后触发。由于这个原因,它的发射时间比我们预期的要晚。不过,如果你考虑的是像IE8这样的老浏览器,这种支持几乎是通用的。当然,您可能需要addEventListener()的polyfill。
<脚本>
窗口。addEventListener(“负载”,函数(e) {
. getelementbyid(“测试”)。addEventListener(“点击”,函数(e) {
console.log(“点击:“,这个);
});
});
> < /脚本
<button id="test">click me</button>
jQuery的准备()
DOMContentLoaded和window:load都有各自的注意事项。jQuery的ready()提供了一个混合的解决方案,在可能的时候使用DOMContentLoaded,在必要的时候切换到window:load,并且在DOM已经完成的时候立即触发它的回调。
你可以把你的现成处理程序直接传递给jQuery作为$(handler),例如:
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script> .
<脚本>
$(函数(){
$(" #测试”).click(函数(){
console.log(“点击:“,这个);
});
});
> < /脚本
<button id="test">click me</button>
选项5:事件委托
将事件处理委托给目标元素的祖先。
当一个元素引发了一个事件(假设它是一个冒泡事件,并且没有任何东西阻止它的传播),该元素祖先中的每个父元素,一直到窗口,也会接收到该事件。这允许我们将一个处理程序附加到一个现有的元素上,并在它们从它的后代中冒泡时对事件进行采样……甚至来自附加处理程序后添加的后代。我们所要做的就是检查事件,看它是否由所需的元素引发,如果是,则运行我们的代码。
通常,这种模式是为加载时不存在的元素保留的,或者是为了避免附加大量重复的处理程序。为了提高效率,选择目标元素最近的可靠祖先,而不是将其附加到文档。
原生JavaScript
< div id =“祖先”> < !——我们的脚本可用的最近的祖先——>
<脚本>
. getelementbyid(“祖先”)。addEventListener(“点击”,函数(e) {
如果(e。target。Id === "后代"){
console.log(“点击:“e。target);
}
});
> < /脚本
<button id="后人">click me</button> .
< / div >
jQuery是()
jQuery通过on()实现了这个功能。给定一个事件名称,一个所需后代的选择器和一个事件处理程序,它将解析您委托的事件处理并管理您的this上下文:
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script> .
< div id =“祖先”> < !——我们的脚本可用的最近的祖先——>
<脚本>
$(" #祖先”)。On ("click", "#后人",函数(e) {
console.log(“点击:“,这个);
});
> < /脚本
<button id="后人">click me</button> .
< / div >