当您的脚本运行时,您尝试查找的元素不在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,一个布尔属性,] 被设置为向浏览器指示脚本将在文档被解析之后但在触发之前执行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>
只要记住…
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事件在
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()
DOMContentLoaded并且
window:load各自有各自的注意事项。jQuery
ready()提供了一种混合解决方案,
DOMContentLoaded在可能的情况下使用,
window:load在必要时进行故障转移,如果 DOM 已经完成,则立即触发其回调。
您可以将准备好的处理程序直接传递给 jQuery ,例如:
$(*handler*)
<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:事件委托
将事件处理委托给目标元素的祖先。
当一个元素引发一个事件(假设它是一个冒泡事件并且没有什么可以阻止它的传播)时,该元素祖先中的每个父元素,一直到
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>


