超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

关于文档加载状态相关的事件探讨

在开发的时候,经常有些操作需要等 DOM 完成后才执行,比如为某个按钮绑定事件、为防止抖动,在页面内容加载完成后才展示页面。这里我们看一下和页面加载有关的一些事件。

window.onload

在文档装载完成后会触发 onload 事件。此时,在文档中的所有对象都在 DOM 中,所有图片,脚本,链接以及子框都完成了装载。

如果在 onload 的事件函数里面,再次监听 onload 事件,那么,是不会再次触发函数的。

function addOnload(callback) {
if (window.addEventListener ){
return window.addEventListener("load", callback, false);
} else {
return window.attachEvent("onload", callback);
}
}

function onload1() {
console.log("First onload executed.");
addOnload(onload2);
}

function onload2() {
console.log("Second onload not executed.");
}

addOnload(onload1);
// "First onload executed."

如上,因此,对于异步加载的JS,为了保证能在 onload 里面触发,可以先判断 document.readystate === "complete",如果成功,则立即执行函数:

function addOnload(callback) {
if ( "complete" == document.readyState ) {
callback();
}
else if (window.addEventListener ){
return window.addEventListener("load", callback, false);
} else {
return window.attachEvent("onload", callback);
}
}

function onload1() {
console.log("First onload executed.");
addOnload(onload2);
}

function onload2() {
console.log("Now, second onload also executed.");
}

addOnload(onload1);
// "First onload executed."
// "Now, second onload also executed."

onload 需要等所有的资源加载完成才执行,当资源过多过大时,onload 会出现比较严重的延迟问题,严重影响用户体验。

DOMContentLoaded

当初始 HTML 文档被完全加载和解析时,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架完成加载。

window.onload = function () {
console.log('onload 后输出');
}
document.addEventListener("DOMContentLoaded", function(event) {
console.log('DOMContentLoaded 先输出');
});

// "DOMContentLoaded 先输出"
// "onload 后输出"

如果 script 标签中包含 defer,那么 HTML 文档构建不受影响,而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。

如果 script 标签中包含 async,则 HTML 文档构建不受影响,解析完毕后,DOMContentLoaded 触发,而不需要等待 async 脚本执行、样式表加载等等。

JQuery 等框架中的 ready 方法实际上主要就是采用 DOMContentLoaded 实现的,而 load 方法监听的是 onload 事件。

该方法在 IE 9- 以下不被支持,但是可以采用下面几节介绍的内容实现。

onreadystatechange 事件

该事件提供与文档的加载状态有关的信息。支持 onreadystatechange 事件的每个对象都有一个 readyState 属性,它的属性包含以下五个值:

  • uninitialized 尚未初始化,对象已经存在但尚未初始化。
  • loading 正在加载中,对象正在加载数据。
  • loaded 加载完毕,对象加载数据完成。
  • interactive 可交互,可以操作 DOM 对象了,但还没有完全加载。
  • complete 完成,对象已经加载完毕。

当这个属性的值变化时,document 对象上的 readystatechange 事件将被触发。如下,回调函数被执行2次:·

document.onreadystatechange = function () {
console.log(document.readyState);
}

// "interactive"
// "complete"

如果在文档的不同状态绑定事件,可以用来模拟 onload 和 近似模拟 DOMContentLoaded 事件。

模拟 onload 事件:

document.onreadystatechange = function () {
if (document.readyState === "complete") {
initApplication();
}
}

不严格情况下能模拟 DOMContentLoaded

document.onreadystatechange = function () {
if (document.readyState === "interactive") {
initApplication();
}
}

实际上 DOMContentLoaded 的执行时机是要比设置 document.readyState = "interactive" 晚的,大概顺序为 interactive 状态 -> 运行defer 脚本和其他阻塞脚本 -> DOMContentLoaded > 运行 async 脚本 -> complete 。具体参考document.readystate of “interactive” vs. ondomcontentloaded?
因此最后一节的模拟 DOMContentLoaded 的实现并未采用这个事件来完成。

doScroll

IE 还有个特有的方法 doScroll,当页面未加载完成时,该方法会报错。

document.documentElement.doScroll("left");

通过间隔调用,直到 doScroll 不再报错时,就代表 DOM 加载完成了,可以用来检测 DOM 是否加载完成。

自定义 DOMContentLoaded 事件

根据上面内容可以提供一个兼容性较好的 DOMContentLoaded 实现:

function domReady(fn){
// 现代浏览器,支持 DOMContentLoaded 事件
if(document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn, false);
}

// IE
else {
// 确保当页面是在iframe中加载时,事件依旧会被安全触发
document.attachEvent('onreadystatechange', function() {
// 如果此时 document 已经 load,立即执行
if(document.readyState == 'complete') {
document.onreadystatechange = null;
fn();
}
});

// 如果页面不在 iframe 中时,轮询调用 doScroll 方法检测 DOM 是否加载完毕
if(document.documentElement.doScroll && typeof window.frameElement === "undefined") {
try {
document.documentElement.doScroll('left');
}
catch(error){
setTimeout(arguments.callee, 20);
return false;
};
fn();
}
}
};

更具体可以参考各个主流框架的实现 主流JS框架中DOMReady事件的实现

参考