浅谈script标签中的async(异步脚本)和defer(延迟脚本)属性:https://www.cnblogs.com/jiasm/p/7683930.html
(1)chrome浏览器中,看到network网络面板中有DOMContentLoaded与load两个数值,分别对应网络请求上相应标志线,这两个时间数值分别代表?
(2)一再强调将css放在头部,将js文件放在尾部(如:</body>之前),这样有利于优化页面性能,为什么这种方式能够优化性能?
(3)在使用jquery时,一般都会将函数调用写在$(document).ready(function() { // ...代码... }); 方法内,是什么原理?
首先,理解DOMContentLoaded
DOMContentLoaded顾名思义,就是dom内容加载完毕。
那什么是dom内容加载完毕?
从打开一个网页说起。当输入一个URL回车后,页面展示首先是空白的;然后一会儿后页面会展示出内容,但是页面的有些资源,比如说图片image资源还无法看到;此时页面是可以正常交互,一段时间后,图片才完成显示在页面。从页面空白到展示出页面内容后,会触发DOMContentLoaded事件。(这段时间HTML文档被加载和解析完成)
这时候问题又来了,什么是HTML文档被加载和解析完成。
先了解下浏览器渲染原理:
当在浏览器地址栏输入URL回车后,浏览器会发送请求到服务器,服务器将请求的HTML文档发送回浏览器,浏览器将文档下载下来后,便开始从上到下解析文档,解析完成之后,会生成DOM。
如果页面中有css,会根据css的内容形成CSSOM,然后DOM和CSSOM会生成一个渲染树,最后浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其绘制在浏览器上。
下面为网页加载和解析过程中,浏览器的一个快照
上面看到解析html过程中,html解析会被中断,因为javascript代码会阻塞dom的解析。
当dom从上而下解析时,遇到<script>标签时,会停止解析;转而去处理<script>脚本,如果脚本是内联的,浏览器会先去执行这段内联的脚本;如果是外链的,那么会先加载脚本js文件,后执行js文件。在处理完js脚本后,浏览器便继续解析HTML文档。
同时,javascript执行会受到标签前面css样式文件的影响。如果在标签前面有css样式文件,需css样式文件加载并解析完后才执行脚本。(这是因为javascript可以查询对象的样式)
注意:在现代浏览器中,为减缓渲染被阻塞的情况,现代浏览器都使用了猜测预加载技术。当解析被阻塞时,浏览器会有一个轻量级HTML(或CSS)扫描器(scanner)继续在文档中扫描,查找那些将来可能能够用到的resource资源文件url,在渲染器使用它们之前将其下载下来。
在这里便明白了DOMContentLoaded所计算的时间,当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等位于脚本前面的css加载完才能执行。
在任何情况下,DOMContentLoaded 的触发不需要等待图片,音频,视频等其他资源加载完成。
其次,说说load
下面来说说load,网页上所有资源(图片,音频,视频等)被加载后才会触发load事件。
页面的load事件会在DOMContentLoaded被触发之后才触发。
jQuery 中使用的 $(document).ready(function() { // ...代码... }); 其实监听的就是 DOMContentLoaded 事件;而 $(document).load(function() { // ...代码... }); 监听的是 load 事件。
在用jquery时,一般都会将函数调用写在ready方法内,就是页面被解析后,我们就可以访问整个页面的所有dom元素,可以缩短页面的可交互时间,提高整个页面体验。
下面来看看如何实现这两个函数
1、onload事件
onload事件所有的浏览器都支持,所以我们不需要什么兼容,只要通过调用。
window.onload = function(){
}
2、DOMContentLoaded 事件
DOMContentLoaded不同的浏览器对其支持不同,所以在实现的时候我们需要做不同浏览器的兼容。
1)支持DOMContentLoaded事件的,就使用DOMContentLoaded事件;
2)IE6、IE7不支持DOMContentLoaded,但它支持onreadystatechange事件,该事件的目的是提供与文档或元素的加载状态有关的信息。
3) 更低的ie还有个特有的方法doScroll, 通过间隔调用:document.documentElement.doScroll("left");可以检测DOM是否加载完成。 当页面未加载完成时,该方法会报错,直到doScroll不再报错时,就代表DOM加载完成了。该方法更接DOMContentLoaded的实现。
function ready(fn){
if(document.addEventListener) {
document.addEventListener('DOMContentLoaded', function() {
document.removeEventListener('DOMContentLoaded',arguments.callee, false);
fn();
}, false);
}
// 如果IE
else if(document.attachEvent) {
// 确保当页面是在iframe中加载时,事件依旧会被安全触发
document.attachEvent('onreadystatechange', function() {
if(document.readyState == 'complete') {
document.detachEvent('onreadystatechange', arguments.callee);
fn();
}
});
// 如果是IE且页面不在iframe中时,轮询调用doScroll 方法检测DOM是否加载完毕
if(document.documentElement.doScroll && typeof window.frameElement === "undefined") {
try{
document.documentElement.doScroll('left');
}
catch(error){
return setTimeout(arguments.callee, 20);
};
fn();
}
}
};
最后来回答问题:为什么一再强调将css放在头部,将js文件放在尾部?
在面试的过程中,经常会有人在回答页面优化中提到将js放到body标签底部(</body>标签之前),原因是因为浏览器生成Dom树时是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。
那么问题来了,既然Dom树完全生成好后页面才能渲染出来,浏览器又必须读完全部HTML才能生成完整的Dom树,script标签不放在body底部是不是也一样,因为dom树的生成需要整个文档解析完毕。
我们再来看一下chrome在页面渲染过程中的,绿色标志线是First Paint的时间。
纳尼,为什么会出现firstpaint,页面的paint不是在渲染树生成之后吗?
其实现代浏览器为了更好的用户体验,渲染引擎将尝试尽快在屏幕上显示的内容。它不会等到所有HTML解析之前开始构建和布局渲染树。部分的内容将被解析并显示。也就是说浏览器能够渲染不完整的dom树和cssom,尽快的减少白屏的时间。假如我们将js放在header,js将阻塞解析dom,dom的内容会影响到First Paint,导致First Paint延后。所以说我们会将js放在后面,以减少First Paint的时间,但是不会减少DOMContentLoaded被触发的时间。