
本教程深入探讨了 DOM 就绪状态、JavaScript 模块(ES Modules)以及 jQuery 的 `$(document).ready()` 方法之间的关系。核心在于,使用 `type="module"` 的脚本会自动延迟执行,这意味着它们会在 HTML 文档解析完毕后才运行。因此,在 ES 模块内部使用 jQuery 的 DOM 就绪方法是多余的,因为当模块代码开始执行时,DOM 已经处于可安全操作的状态。
在现代前端开发中,管理 JavaScript 代码的执行时机,特别是确保在文档对象模型(DOM)完全加载并解析后才进行操作,是至关重要的。不当的脚本执行时机可能导致尝试操作不存在的元素,从而引发错误或不一致的用户体验。本文将详细解析两种常见的脚本加载和执行策略,并结合 JavaScript 模块的特性,阐明何时以及为何某些传统方法变得多余。
理解 DOM 就绪状态
在浏览器渲染网页时,它会逐步解析 HTML 文档,构建 DOM 树。JavaScript 代码通常需要与这个 DOM 树交互,例如查找元素、修改内容或绑定事件。如果在 DOM 元素尚未创建时就尝试对其进行操作,就会失败。因此,开发者需要一种机制来确保 JavaScript 代码在 DOM 准备就绪后才执行。
传统的实现 DOM 就绪的方法有两种:
立即学习“Java免费学习笔记(深入)”;
- window.onload 事件: 这是最古老的方法,它在整个页面(包括所有图片、样式表等外部资源)加载完成后才触发。优点是确保所有资源都可用,缺点是等待时间可能较长。
- jQuery 的 $(document).ready() 或 $(callback): jQuery 提供了一种更高效的机制,它只等待 DOM 结构完全加载和解析,而不等待所有外部资源。这是在 jQuery 盛行时期确保 DOM 就绪的标准方法,其语法简洁,例如 $(function() { /* DOM 操作 */ });。
JavaScript 模块与自动延迟
随着 ES Modules (ESM) 的普及,JavaScript 的加载和执行方式发生了显著变化。当你在 HTML 中使用
type="module" 属性不仅仅是启用 ES 模块语法(如 import/export),它还隐含了一个关键行为:模块脚本是自动延迟(deferred)的。这意味着:
- 非阻塞解析: 浏览器会异步加载模块脚本,不会阻塞 HTML 解析。
- DOM 就绪后执行: 模块脚本会在 HTML 文档完全解析完毕后,但在 DOMContentLoaded 事件触发之前执行(或非常接近)。这与在
这种自动延迟的特性是理解后续内容的关键。
两种脚本执行方式的对比
现在,我们来对比两种在 type="module" 脚本中执行 DOM 操作的方式:
方式一:直接执行 DOM 操作
// home.js
import $ from "jquery";
import { getCurrentYear } from "../utils/global/functions";
// 直接在模块的顶层作用域执行 DOM 操作
$("#year").text(getCurrentYear());在这种方式中,代码 $("#year").text(getCurrentYear()); 会在 home.js 模块被加载和执行时直接运行。由于 home.js 是一个 type="module" 脚本,它已经保证了会在 HTML 解析完毕、DOM 树构建完成后才执行。因此,当这行代码执行时,ID 为 year 的元素已经存在于 DOM 中,可以安全地进行操作。
方式二:使用 jQuery 的 DOM 就绪方法
// home.js
import $ from "jquery";
import { getCurrentYear } from "../utils/global/functions";
function setCopyrightYear() {
$("#year").text(getCurrentYear());
}
// 将函数传递给 jQuery,等待 DOM 就绪
$(setCopyrightYear);在这种方式中,我们定义了一个函数 setCopyrightYear,然后将其作为参数传递给 jQuery 的 $() 方法。这等价于 $(document).ready(setCopyrightYear);,其目的是确保 setCopyrightYear 函数在 DOM 就绪后才执行。
为什么方式二在模块脚本中是冗余的?
结合之前对 JavaScript 模块特性的理解,我们可以得出结论:在 type="module" 脚本中使用 jQuery 的 $(document).ready() 或其简写形式是多余的。
原因是:
- 模块的自动延迟: 当浏览器加载 时,它会等待整个 HTML 文档解析完毕后才开始执行 home.js 中的代码。
- DOM 已经就绪: 到 home.js 中的代码(包括 $(setCopyrightYear); 这行)开始执行时,DOM 已经完全构建完成,处于可操作状态。
- 立即执行回调: 在 DOM 已经就绪的情况下,jQuery 的 $(document).ready() 方法会立即执行其回调函数,而不会等待任何额外事件。
因此,$(setCopyrightYear); 实际上并没有提供额外的 DOM 就绪保障,它只是在 DOM 已经就绪的情况下,立即调度 setCopyrightYear 函数执行。这与方式一中直接执行代码的效果完全相同,但增加了不必要的包装层。
最佳实践与总结
对于使用 ES Modules 进行现代前端开发,推荐的实践是:
-
直接执行 DOM 操作: 在 type="module" 的脚本中,如果你的代码需要操作 DOM,可以直接在模块的顶层作用域或导入的函数中执行这些操作。浏览器会自动确保脚本在 DOM 可用时才运行。
// home.js import $ from "jquery"; import { updateUI } from './ui-logic'; // 假设 updateUI 包含 DOM 操作 // 当模块加载时,DOM 已就绪,直接调用 updateUI(); $('#someElement').on('click', handler); -
何时仍需 $(document).ready()?
- 非模块脚本: 如果你还在使用传统的
- 动态加载的非模块脚本: 如果通过 JavaScript 动态创建并插入到 DOM 中的
总而言之,ES Modules 的自动延迟特性极大地简化了 DOM 就绪的管理。通过利用这一特性,我们可以编写更简洁、更直接的代码,避免不必要的抽象层,从而提高代码的可读性和维护性。在现代项目中,优先使用 type="module" 结合直接执行策略,将是更高效和优雅的选择。










