JavaScript 时间线
一、JS时间线(ppt)
1、ppt
01/
创建Document对象,开始解析web页面,
解析<HTML>元素和他们的文本内容后添加Element对象和Text节点到文档中。
这个阶段 document.readyState = "loading"
02/
遇到link外部css,创建线程加载,并继续解析文档
03/
遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行脚本
04/
遇到script外部js,并设置有async、defer,浏览器创建线程加载,并继续解析文档,
对于async属性的脚本,脚本加载完成后立即执行(异步执行禁止使用document.write())
05/
遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
06/
当文档解析完成,document.readyState = "interactive"
07/
文档解析完成后,所有设置有defer的脚本会按照顺序执行(注意defer与async的不同,但同样禁止使用document.write())
08/
document对象触发DOMContentLoaded事件,这标志着程序从同步脚本执行节点,转化为事件驱动阶段。
09/
当所有async的脚本加载完成并执行后,img等加载完成后,document.readyState = "complete",window对象触发load事件
10/
从此、以异步响应方式处理用户输入、网络事件等
2、JS时间线
这个时间线是一个理论依据,学完它也编不了程,也做不了其他事,但为以后的优化一些东西,做一个基本的理论支持,
这东西很重要,有可能现在用不着,但以后很重要必须要要懂。
时间线一共就十步,其实简化来说就九部,再简化点也就四五步
3、为什么叫JS时间线呢?
他是根据js出生那一刻开始,记录了一系列浏览器按照顺序做的事
1). 浏览器在刚刚运行一个页面的时候,他首先会初始化js的功能,
2). 当初始完js功能开始,也就是说js开始发挥他的作用,开始那一刻,
开始记载着这一系列浏览器接下来要发生的过程,每一步顺序的事,这个叫做浏览器的加载时间线,
换句话说js时间线形容的就是一个执行顺序,谁必须发生在谁之前,谁在谁之后发生
3). 发生顺序的起始点就是浏览器创建一个docuemtn对象,有了docuemnt之后才意味着js诞生了(意味着js好使了)
其实js加载时间线就可以理解成时间线就可以了,js只是一个形容词,而这个形容词形容的又很不准确,
就理解为时间线就可以了,浏览器加载的时间线。
二、浏览器加载时间线十步
第一步
1). 创建一个Document对象(就是那个小的document代表文档的东西)然后开始解析web页面,
2). 开始解析web页面,解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中(这行忽略)。
3). 这个阶段document.readyState的状态是loading( document.readyState = 'loading' )。
第二步
1). 遇到link引入的外部css,创建一个新的线程进行异步加载,
2). 并继续解析文档。
第三步
1). 遇到script标签外部加载js,并且没有async、defer是同步加载,
2). 阻塞整个页面,等待js加载完成并执行该脚本,
3). 然后继续解析文档。
PS:
现在唯一遇到的新知识点就是第1步,先生成了一个Document对象,document.readyStete上状态位变成loading。
第四步
遇到script外部js并且设置有async、defer的时候,
1). 浏览器开启新的线程去加载因为是异步的,
2). 并且继续解析文档(HTML、css)不阻碍文档的解析,
3). 对于async属性脚本,脚本加载完之后立即执行,defer要等到文档解析完才执行,
PS:
这块有一个禁止,异步加载的js里面禁止是使用document.write()方法
document.write方法非常特殊,他是把里面写的东西当中HTML文档输出到页面,但他有一个特别特殊的地方,
当整个文档全部都解析的差不多的时候,再调用document.write会把之前所有的文档流都清空,用document.write里面的文档流进行代替,什么意思呢?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document.write()非常特殊</title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> document.write('a'); </script> </body> </html>
红色方块下面出现'a'
1). 首先、先解析html文档再解析css,
2). 然后页面的randerTree基本也绘制的差不多了,
但是还没有开始完全绘制文档,只不过把div高和宽预留出来了,基本上条条框框已经有了位置定好了,
3). 所以下面document.write的时候,会把'a'当中文档流输出到页面里面去。
但是如果写的是window.onload里面输出'a',
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document.write() 非常特殊</title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> window.onload = function(){ document.write('a'); } </script> </body> </html>
1). window.onload是要等整个页面所有的东西,全加载并解析完后才执行的,
2). 这个时候在document.write('a'),
那不好意思就他会实现一个非常强大的功能,把所有之前的文档流全部清空,
3). 然后打印出 document.write('a') 里面的东西 'a'
也就是说document.write在一定情况下能消除文档流的功能(什么是文档?写到html里面的东西全叫文档)
消除到一个什么程度呢?把自己的script标签都删了
在整个文档全部加载完之后document.write能消除文档流,
还有一种情况在异步加载js文件的时候,如果异步的文档里有document.write,也会实现同样的功能,把文档清空了,
所以异步里面是禁止使用document.write,因为其他脚本还没有加载完呢。
这是第四步加载异步脚本。
第五步
1). 遇到img等标签,先正常解析dom结构,然后浏览器异步加载src里面的内容,
2). 并且继续解析文档。
第六步
当文档解析完成的时候( 什么是文档解析完成?domTree刚刚建立完 ),
文档解析完不代表加载完,解析完发生在加载完之前,
当文档解析完时候发生一个改变document.readyState等于interative变成活跃的、动态的( document.readyState = 'interative' )
第七步
当文档解析完毕之后( 其实和第六步同时发生的 ),设置有defer的脚本会按照他们的出生顺序去执行,
也就是说defer脚本要等到文档解析完毕才会执行,
什么时候文档解析完毕呢?
他会监控document.readyState变成interative时候,defer才会执行,
也就是说defer先去下载,下载完之后等着,defer是推迟的意思推迟执行,推迟到文档解析完毕。
第八步
第八步讲的还是第六步、第七步一样的事,
当文件解析完毕之后,dcoument对象触发一个事件叫DOMComtentLoaded事件,
触发完这个事件也就标志着程序从 同步脚本执行阶段 转化为 事件驱动阶段。
换句话说,
1). 之前的阶段是有一个脚本,就去阻塞页面同步执行的阶段,
2). 之后的阶段叫浏览器可以监听事件了,监听用户的输入事件、点击事件等,
也就是说从文档解析完毕之后,浏览器就发挥出了它正常应有的功能,开始能识别事件了。
第九步
第九步也就是最后一步,
1). 当所有async的脚本加载完毕并执行完毕之后,img等加载完毕之后,这时候都完事之后,
换句话说当页面所有的部分都下载并执行完毕之后,document.readyState再变一次变成complete( document.readyState = complete ),
2). 这个时window对象会触发load事件。
第十步
从此之后,页面按照正常的我们理解的方式去运转了。
三、重新说一遍
整个时间线其实就三点
第一点:创建Document对象
第二点:文档解析完了
第三点:文档解析完并加载完了,并且执行完了
主要就是这三步骤,围绕着这三个点来进行讨论的,在这三步里面穿插点东西
1、第1步
先创建小写的document对象,这时候 document.readyState = 'loading'
2、第2步到第4部
当遇到link标签怎么解析、当遇到script标签怎么解析、当遇到异步的script标签怎么去解析,讨论的是这些事。
然后这些事都完事之后,可能还会遇到img等标签带src属性的,
1). 遇到有src的属性的标签,整个页面会开启一个异步的线程,加载src里面的内容,
2). 并且会同时会继续解析下面的文档。
等到这些该解析解析完了之后,到了一个节点叫 文档全部解析完毕 DOM树构建完。
3、DOM树构建完了之后形成
1). 第一个关键是 document.readyState = 'interactive',
2). 第二个标准性的事件是当文档解析完之后,所有defer加载的脚本会按照他们的出生顺序去执行,
但是defer脚本一定会等到文档解析完毕之后才会执行。
3). 第八条当文档解析完毕之后,document还会触发一个事件叫DOMContentLoaded事件,
触发完这个事件之后,也就标准着从一个时代跨国另一个时代,
程序从正常同步脚本执行阶段转会为异步事件驱动阶段。
4). 第九条当async脚本加载并执行完毕、img等标签加载完毕之后,也就是说所有东西全部都加载并执行完毕之后,
这时候触发 document.readyState = 'complete',同时widonw对象触发load事件。
这是整体的时间线,其实整体就三点创建document对象、解析、然后执行并加载完。
四、验证时间线的过程
围绕document.readyState来看一下时间线的过程,
document.readyState = loading 一直在变,loading -> interactive -> complete
单独写一个document.readyState打印出的是什么?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> console.log(document.readyState); // loading </script> </body> </html>
什么时候变成interactive?
1). 当文档解析完,文档解析完要形成一个DOM树,生成DOM树至少要把最后一个DOM结构解析完,
2). 而最后一个又是script标签自己,script标签也是DOM节点(script标签都能生成),
所以script自己解析完成之后,才能让document.readyState变成interactive
3). 但是在script里面DOM树还没有完全构建完毕,所以现在打印完的还是最开始的loading
就想看interactive怎么办?在下面再写一个script标签
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> console.log(document.readyState); // loading </script> <!-- 再写一个script标签 --> <script> console.log(document.readyState); // loading </script> </body> </html>
还是loading,因为再写script标签还是一个dom结构
问题天使:
在HTML外面写script,还是script标签(这个方法不行我也这么简单的想过)
用window.onload事件之后,打印结果是变了,变成complete
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> window.onload = function(){ console.log(document.readyState); // complete } </script> </body> </html>
结果是complete不是interactive,就想打印interactive
在js异步下载时候学过,凡是readysState都有一个监听事件onreadystatechange( 注意一点:所有的事件都没有大写的时候,这也是事件和属性的一个区别 )。
在document上绑定一个onreadystatechange事件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> document.onreadystatechange = function(){ console.log(document.readyState); // interactive compltate } </script> </body> </html>
当readyState变的时候就把document.readyState打印出来,结果是interactive和complete
为什么没有loading?
初始的时候就是loading,而事件onreadystatechange一触发readyState就变值了,
还要把document.readyState单独打印
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> // 把document.readyState单独打印 console.log(document.readyState); // loading document.onreadystatechange = function(){ console.log(document.readyState); // interactive compltate } </script> </body> </html>
这回三个状态都出来了
这里面还有些事件
1). window.onload事件我们知道,
2). document上还有一个DOMContentLoaded事件,也是在文档解析完出现的
document.onDOMContentLoaded 事件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> document.onDOMContentLoaded = function(){ console.log('a'); } </script> </body> </html>
什么都没打印,不是DOMContentLoaded事件不好用,是这个事件有点特殊
DOMContentLoaded事件只在addEventListener上面绑定有效果
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> console.log(document.readyState); // loading document.onreadystatechange = function(){ console.log(document.readyState); // interactive compltate } document.addEventListener('DOMContentLoaded', function(){ console.log('a'); // a }, false); </script> </body> </html>
打印出'a'了,而且'a'紧接着发生在interactive之后,相当于这两个并行发生的
说一个基本概念:
为什么要把script标签写在文档最下面是最合适的情况?
script一般是要操作dom的,写在最下面最后才执行,意味着上面的dom已经处理完事了,所以写到最后的位置是最科学的。
但是又不是那么很科学,scipt写到最后说明前面的dom结构全完事了,
但是在处理的时候其实没有完全等到dom树全部都构建完才去处理,
因为最后一步是script标签,但是script标签是不会处理自己,所以script标签写到最后也就是投机取巧。
其实最好的方式有点类似于window.onload,等页面全部处理完后再处理,
但window.onload是土鳖写法,因为要等页面全下载完才触发。
最好方法是等dom树解析完成 然后在操作,
有一种方法监听dom解析完,然后再执行js,DOMContentLoaded就是这样的方法。
面试题经常会问到jQuery的$(function(){}) 和 window.onload的区别,
1). window.onlaod 要完全加载完才执行,比较慢
2). jQuery的$(function(){}) 要等到解析完才执行,更好一些
$(document).ready(function(){ // 里面写当dom解析完就执行的部分 });
jQuery的 $(function(){}) 底层原理就是依据
1). document.readyState变成interactive
2). DOMContentLoaded事件
document.addEventListener('DOMContentLoaded', function(){ console.log('a'); }, false);
模拟jquery
把script标签写在上面,也可以选出下面的div
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>js时间线</title> <script> document.addEventListener('DOMContentLoaded' ,function(){ var div = document.getElementsByTagName('div')[0]; console.log(div); }, false) </script> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> </body> </html>
script标签写在上面更美观些,但是要能处理下面的代码,
而且这样效率更高,因为等dom都解析完才去执行,不用等到dom加载完才执行。
当然通用的做法还是把script标签写在最下面,因为实在是没有比这更快的方式了。
这就是js时间线,
js时间线要背也背下来这个东西非常重,这个会为未来优化一些DOM,优化一些操作,提供一个很坚实的理论基础。