页面声明周期
1. DOMContentLoaded 事件
DOMContentLoaded
—— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img>
和样式表之类的外部资源可能尚未加载完成
DOMContentLoaded
事件发生在 document
对象上,必须使用 addEventListener
来捕获
- 具有
async
特性(attribute)的脚本不会阻塞DOMContentLoaded
- 使用
document.createElement('script')
动态生成并添加到网页的脚本也不会阻塞DOMContentLoaded
- 外部样式表以及图片和其他资源不会阻塞
DOMContentLoaded
<script>...</script>
或<script src="..."></script>
之类的脚本会阻塞DOMContentLoaded
,浏览器将等待它们执行结束
<style>
img {
width: auto;
}
</style>
<script>
function read() {
// 图片以及样式不会阻塞加载
console.log(img.offsetWidth, img.offsetHeight); // 0 0
console.log('DOM ready');
console.log(window.getComputedStyle(img).width); // 0px
}
// DOMContentLoaded 发生在document对象上
document.addEventListener("DOMContentLoaded", read)
</script>
<script>
// script脚本文件会阻塞加载
// 必须等待script甲苯加载完成,然后加载
console.log('script')
</script>
<img id="img" src="https://w.wallhaven.cc/full/x8/wallhaven-x8ye3z.jpg" alt="">
2. load事件
当整个页面,包括样式、图片和其他资源被加载完成时,会触发 window
对象上的 load
事件
当页面未加载完成时,script
标签如果在文档上,js
脚本中将无法获取到元素,可以通过 onload
事件等文档元素加载完成再使用
<style>
.box {
width: 200px;
height: 200px;
}
</style>
<script>
// script标签阻塞先执行,文档还未加载完,获取为null
let box = document.querySelector('.box');
console.log(box); // null
// onload事件等样式图片其它资源加载完成后执行
window.onload = function() {
let box = document.querySelector('.box');
console.log(box);
// <div class="box"></div>
}
</script>
<div class="box"></div>
3. beforeunload 事件
如果访问者触发了离开页面的导航或试图关闭窗口,beforeunload
事件将会触发
event.preventDefault()
在 beforeunload
处理程序中不起作用
window.onbeforeunload = function() {
return false;
};
window.onbeforeunload = function(e) {
// 阻止无效
e.preventDefault();
return "有未保存的值。确认要离开吗?";
};
4. unload 事件
与 load
事件相对的 unload
事件,unload
事件会在文档卸载完成后触发
unload
事件一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以免内存泄露
页面资源的清除工作会在 unload
事件之后进行
window.addEventListener("unload", function() {})
5. readyState 状态
document.readyState
属性提供当前加载状态的信息
loading
—— 文档正在被加载interactive
—— 文档被全部读取complete
—— 文档被全部读取,并且所有资源(例如图片等)都已加载完成
console.log(document.readyState); // loading
// readyState获取文档加载状态
document.addEventListener('DOMContentLoaded', function () {
console.log(document.readyState); // interactive
})
window.onload = function () {
console.log(document.readyState); // complete
}
6. resize 事件
window.onresize = function(){}
当浏览器窗口被缩放到新的高度或者宽度时,会触发 resize
事件
这个事件在 window
上触发,需要通过在 window
或者 body
上添加 onresize
事件来指定事件处理程序
// onresize 当浏览器窗口高度宽度发生变化时触发
window.addEventListener("resize", function() {
console.log('触发')
})
7. error 事件
- 在
window
上,当js
脚本报错时触发 - 在
img
元素上,当无法加载指定图片时触发 - 在元素对象上,无法加载对应对象时触发
- 在多窗口下,一个或多个窗口无法完成加载时触发
// 当js脚本发生错误时则执行error事件
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // 没有这个脚本
document.head.append(script);
script.onerror = function() {
console.log("Error loading " + this.src); // Error loading https://example.com/404.js
};
// 当图片无法加载时执行error事件
let img = document.createElement('img');
img.src = "https://js.cx/clipart/traisn.gif";
img.onload = function() {
console.log('加载失败')
};
img.onerror = function() {
console.log(`Error loading` + this.src);
};
8. async,defer
当浏览器加载 HTML 时遇到 <script>...</script>
标签,浏览器就不能继续构建 DOM
,它必须立刻执行此脚本
对于外部脚本 <script src="..."></script>
浏览器必须等脚本下载完,并执行结束,之后才能继续处理剩余的页面
该机制导致的问题:
-
脚本不能访问到位于它们下面的
DOM
元素,因此,脚本无法给它们添加处理程序等 -
如果页面顶部有一个笨重的脚本,它会阻塞页面,在该脚本下载并执行结束前,用户都不能看到页面内容
解决方案:
- 把
js
脚本放在文档标签底部,它可以访问到它上面的元素,并且不会阻塞页面显示内容
<script>
// 脚本不能访问到位于它们下面的 DOM 元素
let box = document.querySelector('.box')
console.log(box); // null
</script>
<div class="box"></div>
<!-- 放到dom元素下面来获取 -->
<script>
// 脚本不能访问到位于它们下面的 DOM 元素
let box1 = document.querySelector('.box')
console.log(box1); // <div class="box"></div>
</script>
8-1 defer
defer
特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML
,构建 DOM
。脚本会在后台下载,然后等 DOM
构建完成后,脚本才会执行
**Tips:**如果
<script>
脚本没有src
,则会忽略defer
特性
- 具有
defer
特性的脚本不会阻塞页面 - 具有
defer
特性的脚本总是要等到DOM
解析完毕,但在DOMContentLoaded
事件之前执行 - 具有
defer
特性的脚本按照文档顺序加载(它们在文档中的顺序)
<script>
document.addEventListener("DOMContentLoaded", function () {
console.log('DOMContentLoaded')
})
</script>
<script defer src="./index.js"></script>
<!-- defer 先执行 会等待dom解析完毕 -->
<!-- 能获取到dom元素 -->
<!-- 文档中的顺序加载 -->
<script defer src="./last.js"></script>
8-2 async
async
特性与 defer
有些类似,也能够让脚本不阻塞页面
**Tips:**如果
<script>
脚本没有src
,则会忽略async
特性
-
浏览器不会因
async
脚本而阻塞 -
其他脚本不会等待
async
脚本加载完成,async
脚本也不会等待其他脚本 -
具有
async
特性的脚本按照加载优先顺序,脚本在文档中的顺序不重要 —— 先加载完成的先执行 -
DOMContentLoaded
和异步脚本不会彼此等待-
DOMContentLoaded
可能会发生在异步脚本之前(异步脚本在页面完成后才加载完成) -
DOMContentLoaded
也可能发生在异步脚本之后(异步脚本很短,或者是从 HTTP 缓存中加载的)
-
<script>
document.addEventListener("DOMContentLoaded", function () {
// async 与 DOMContentLoaded 两者互不等待
// 有可能 async 在 DOMContentLoaded 前执行
// 有可能 async 在 DOMContentLoaded 后执行
setTimeout(function () {
console.log('DOMContentLoaded')
}, 1000)
})
</script>
<script async src="./index.js"></script>
<!-- async 能获取到dom元素 -->
<!-- 加载优先顺序 -->
<script async src="./last.js"></script>
8-3 动态脚本
使用 JavaScript
动态地创建一个脚本,并将其添加到文档中,默认情况下,动态脚本的行为是异步的
- 它们不会等待任何东西,也没有什么东西会等它们
- 先加载完成的脚本先执行(加载优先顺序)
- 也可通过
script.async = false
来改变加载规则,脚本将按照脚本在文档中的顺序执行
<script>
document.addEventListener("DOMContentLoaded", function () {
setTimeout(function () {
console.log('DOMContentLoaded')
}, 3000)
})
</script>
<div class="box"></div>
<script>
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false;
document.body.append(script);
}
loadScript("./index.js");
loadScript("./last.js");
</script>
// index.js
setTimeout(() => {
console.log('index.js');
console.log(document.querySelector('.box'));
}, 1000)
// last.js
setTimeout(() => {
console.log('last.js')
}, 1000)