HTML 原生生命周期(Lifecycle)通常是指与 HTML 相关的事件和浏览器在加载和处理网页时经历的各个阶段。尽管 HTML 本身是一个标记语言,没有像 JavaScript 这样的生命周期钩子,但 HTML 的生命周期事件实际上是通过 JavaScript 与 DOM(文档对象模型)交互来管理的。
解析 HTML(HTML Parsing)
当浏览器加载一个网页时,它会从服务器接收 HTML 文件并开始解析。在这个阶段,浏览器会创建 DOM 树(Document Object Model)结构,并将 HTML 转换为可操作的 DOM 对象。
严格来说,解析 HTML 是页面加载过程中的一个重要阶段,但它并不直接属于传统意义上的“生命周期事件”范畴,因为它不是通过 JavaScript 可以捕获或监听的事件。然而,从更广义的角度来看,解析 HTML 是整个页面生命周期中不可或缺的一部分,因此在讨论 HTML 生命周期时,它是非常关键的。
这一过程属于浏览器内部的过程,开发者无法直接监听到这一阶段,但可以通过优化 HTML 结构、减少阻塞的资源加载(如 JS 文件)来提高解析速度。
加载外部资源(Resource Loading)
在浏览器解析 HTML 的过程中,遇到外部资源时,它会根据资源的类型、加载方式(同步或异步)以及优先级,来决定如何继续加载和渲染页面。这种行为会直接影响页面渲染的顺序以及用户看到的内容的加载时间。
不同类型的资源有不同的加载行为,它们对页面解析和渲染的影响也不同
-
css 加载: 浏览器遇到
<link>
标签时,会暂停页面的渲染,直到 CSS 文件被完全加载并解析。CSS 被视为阻塞渲染的资源,因为页面布局和样式在未加载 CSS 文件时无法正确渲染。 -
JavaScript 加载:默认情况下,当浏览器遇到
<script>
标签时,它会暂停 HTML 的解析,直到 JavaScript 文件被加载并执行完成。这种行为称为同步加载。同步加载的 JavaScript 会阻塞 HTML 的解析,因此会影响 DOMContentLoaded 和 load 事件的触发时间。
总的来说,加载外部资源和页面的生命周期密切相关,因为外部资源的加载会影响页面的解析、渲染以及关键生命周期事件(如 DOMContentLoaded 和 load)的触发时间。外部资源的加载时间越短,生命周期事件触发得越快。
readyState & readystatechange
readyState 和 readystatechange 是浏览器的两个关键属性和事件,常用于跟踪文档和网络请求(如 AJAX 请求)的状态,帮助开发者了解网页加载过程中的不同阶段,以及如何在这些阶段执行相应的操作。它们主要应用在文档加载和网络请求(如 XMLHttpRequest)的上下文中。
document.readyState 是一个属性,用来表示当前文档的加载状态。它具有三个可能的状态值,分别代表文档的不同加载阶段:
-
loading:表示文档正在加载中,HTML 还在解析过程中,DOM 树尚未完全构建。这时外部资源(如图片、样式表等)可能还未加载或处理。
-
interactive:表示文档的 HTML 已经完全加载和解析完毕,DOM 树已经构建完成,但样式表、图片、子框架等资源可能还没有完全加载。
-
complete:表示页面的所有资源(包括 HTML、CSS、JavaScript、图片、子框架等)都已完全加载并处理,页面已经完全准备好。
通过 document.readyState,开发者可以检查当前文档的加载状态,并基于不同的状态执行相应的操作。例如:
if (document.readyState === "complete") {
// 页面完全加载完成,可以执行页面操作
}
readystatechange 是与 readyState 属性相关的事件,它在文档的加载状态(readyState)发生变化时触发。开发者可以监听 readystatechange 事件,以便在文档的不同加载阶段执行特定的逻辑。如下代码所示:
document.addEventListener("readystatechange", function () {
if (document.readyState === "interactive") {
// DOM 树已经构建完成,可以操作 DOM
console.log("DOM 已经完全解析完毕");
} else if (document.readyState === "complete") {
// 页面完全加载完毕,包括所有资源
console.log("页面和资源已经完全加载");
}
});
下面是一个详细的 HTML 代码示例,展示了如何使用 document.readyState 和 readystatechange 事件来跟踪文档加载的不同阶段。页面包括一些基本的 HTML 元素,并且在不同的 readyState 状态下显示相应的内容或信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document ReadyState Example</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
.status {
font-size: 1.2em;
color: #333;
margin: 20px 0;
}
img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<h1>嗨 靓仔</h1>
<script>
function updateStatus() {
console.log(document.readyState);
switch (document.readyState) {
case "loading":
console.log("loading");
break;
case "interactive":
console.log("interactive");
break;
case "complete":
console.log("complete");
break;
}
}
updateStatus();
document.addEventListener("readystatechange", updateStatus);
</script>
</body>
</html>
最终代码显示效果如下图所示:
DOMContentLoaded 事件
DOMContentLoaded 事件是浏览器在加载 HTML 文档时触发的一个关键事件,它表示 HTML 文档的所有元素已经被完全解析,DOM 树已经构建完成,但外部资源(如图片、样式表、视频等)可能还没有完全加载。这是与 load 事件的一个主要区别。DOMContentLoaded 事件发生在 document 对象上。我们必须使用 addEventListener 来捕获它:
document.addEventListener("DOMContentLoaded", () => {});
DOMContentLoaded 事件触发的时机是当浏览器解析完 HTML 文档,并且所有的 DOM 节点已经生成。并不要求外部资源(如图片、视频、样式表、字体文件等)不需要完全加载完毕。
举个例子,如果页面中包含了一个较大的图片,DOMContentLoaded 事件会在图片加载之前就触发,但此时 DOM 树已经完全构建,开发者可以操作和访问页面中的 DOM 元素。如下代码所示:
<script type="text/javascript">
function ready() {
console.log("DOM is ready.");
const img = document.querySelector("#img");
// 图片目前尚未加载完成(除非已经被缓存),所以图片大小为 0x0
console.log(`图片大小: ${img.offsetWidth}x${img.offsetHeight}`);
}
document.addEventListener("DOMContentLoaded", ready);
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0" />
最终效果如下图所示,结果先输出了,图片在最后才显示:
如果页面中有同步的 JavaScript 文件(即没有使用 async 或 defer),浏览器会在遇到 <script>
标签时暂停 HTML 解析,等待脚本执行完毕再继续解析。这会延迟 DOMContentLoaded 事件的触发。
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM ready!");
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
<script type="text/javascript">
console.log("Library loaded, inline script executed");
</script>
它的加载输出顺序:
-
Library loaded...
-
DOM ready!
总的来说,不会阻塞 DOMContentLoaded 的脚本是具有 async 特性的脚本不会阻塞 DOMContentLoaded 和使用 document.createElement('script') 动态生成并添加到网页的脚本也不会阻塞 DOMContentLoaded。
window.onload 事件
当整个页面,包括样式、图片和其他资源被加载完成时,会触发 window 对象上的 load 事件。可以通过 onload 属性获取此事件。
下面的这个示例正确显示了图片大小,因为 window.onload 会等待所有图片加载完毕:
<script type="text/javascript">
window.onload = function () {
console.log("Page loaded");
// 此时图片已经加载完成
console.log(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
};
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0" />
这里跟之前不同的是,onload 会等到图片等资源完全加载完成之后才输出,这个时候我们是可以获取到图片的宽高的:
window.onbeforeunload 事件
beforeunload 事件在页面即将离开之前(即浏览器即将卸载页面,用户导航到其他页面、关闭页面或刷新页面)触发。该事件允许开发者提示用户是否确定离开当前页面,通常用于在用户离开前保存未保存的数据,或者提醒用户可能会丢失工作。
浏览器允许在这个事件中显示一条消息,询问用户是否要离开页面。例如,当用户在某个未保存的表单中输入了内容时,开发者可以使用 beforeunload 来阻止用户意外关闭或刷新页面。
现代浏览器不会显示自定义的提示信息,只显示一条默认的警告文本。浏览器的默认行为是显示一个标准的对话框,用户可以选择继续离开或停留在当前页面。
window.onbeforeunload = function () {
return false;
};
在这个示例中,beforeunload 事件被监听。当用户试图离开页面时,浏览器会弹出一个警告对话框,询问用户是否真的要离开页面。
由于安全和用户体验的考虑,浏览器会忽略大部分自定义的提示信息,通常会显示统一的对话框。此外,频繁或滥用 beforeunload 可能会导致用户体验不佳,因此建议只在确实有需要时使用,例如在数据未保存的情况下。
unload 事件
unload 事件在页面完全卸载时触发,也就是说页面已经关闭、刷新或者导航到新的页面时发生。与 beforeunload 不同,unload 事件是无法阻止的,它只能用于执行一些最终的清理操作。该事件主要用于在页面卸载之前清除临时数据、取消异步请求、释放内存等操作。
它的主要特点是 unload 事件不能像 beforeunload 那样阻止用户离开页面,也无法提示用户。它更多的是用来执行资源清理操作,如关闭 WebSocket、保存数据到本地存储、清理定时器等。
除此之外,我们还可以在那里做一些 不涉及延迟
的操作,例如发送分析数据。
假设我们收集有关页面使用情况的数据:鼠标点击、滚动、被查看的页面区域等。自然地,当用户要离开的时候,我们希望通过 unload 事件将数据保存到我们的服务器上。有一个特殊的 navigator.sendBeacon(url, data) 方法可以满足这种需求,它在后台发送数据,转换到另外一个页面不会有延迟:浏览器离开页面,但仍然在执行 sendBeacon:
const analyticsData = {
// 带有收集的数据的对象
};
window.addEventListener('unload', function() {
navigator.sendBeacon('/analytics', JSON.stringify(analyticsData));
};
当 sendBeacon 请求完成时,浏览器可能已经离开了文档,所以就无法获取服务器响应(对于分析数据来说通常为空)
总结
HTML 解析是页面生命周期的基础,但它本身不属于 JavaScript 可监听的生命周期事件。DOMContentLoaded 事件是在 DOM 树构建完成时触发的,load 事件则在页面的所有资源完全加载后触发。beforeunload 事件用于在用户离开页面前提示他们保存工作,而 unload 事件则用于页面卸载时进行资源清理。这些事件为开发者提供了控制页面加载和关闭过程的机会,有助于提升用户体验和页面性能。
最后分享两个我的两个开源项目,它们分别是:
这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗