JavaScript 时间线
一、什么是 js 时间线
这个时间线是一个理论依据,
学完它也编不了程,也做不了其他事,但是能为我们以今后的优化一些东西,做一个基本的理论支持,
这东西很重要,有可能现在用不着,但以后很重要,必须要要懂。
时间线一共就十步,其实简化来说就九部,再简化点也就四五步
为什么叫 js 时间线呢?(它是根据 js 出生那一刻开始,记录了一系列浏览器按照顺序做的事)
1. 浏览器在刚刚运行一个页面的时候,它首先会初始化 js 的功能,
2. 当初始完 js 功能开始,也就是说 js 开始发挥他的作用开始那一刻,开始记载着这一系列浏览器接下来要发生的过程,每一步顺序的事,
这个叫做浏览器的加载时间线,
换句话说,
js 时间线形容的就是一个加载顺序,或者说一个执行顺序,
谁必须发生在谁之前,谁在谁之后发生,
发生顺序的起始点就是浏览器创建一个 docuemtn 对象,有了 docuemnt 之后才意味着 js 诞生了,才意味着js好使了
其实 js 加载时间线就可以理解成时间线就可以了,js 只是一个形容词,而这个形容词形容的又很不准确,
就理解为时间线就可以了,浏览器加载的时间线。
js 时间线(课程ppt)
1. 创建 Document 对象,开始解析 web 页面。
解析 <HTML> 元素和他们的文本内容后添加 Element 对象和 Text 节点到文档中。这个阶段 document.readyState = "loading"
2. 遇到 link 外部 css,创建线程加载,并继续解析文档。
3. 遇到 script 外部 js,并且没有设置 async、defer,浏览器加载,并阻塞,等待 js 加载完成并执行脚本,然后继续解析文档。
4. 遇到 script 外部 js,并设置有 async、defer,浏览器创建线程加载,并继续解析文档。
对于 async 属性的脚本,脚本加载完成后立即执行(异步执行禁止使用document.write())
5. 遇到 img 等,先正常解析 dom 结构,然后浏览器异步加载 src,并继续解析文档。
6. 当文档解析完成, document.readyState = "interactive"
7. 文档解析完成后,所有设置有 defer 的脚本会按照顺序执行。
注意,
defe r与 async 的不同,但同样禁止使用 document.write())
8. document 对象触发 DOMContentLoaded 事件,这标志着程序从同步脚本执行节点,转化为事件驱动阶段。
9. 当所有 async 的脚本加载完成并执行后,img 等加载完成后, document.readyState = "complete" ,window 对象触发 load 事件
10. 从此、以异步响应方式处理用户输入、网络事件等
二、详解,浏览器加载时间线十步
第一步
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. 然后继续解析文档。
现在唯一遇到的新知识点就是第1步,
先生成了一个 Document 对象,document.readyStete 上状态位变成 loading。
第四步
遇到 script 外部 js 并且设置有 async 或 defer 的时候,
1. 浏览器开启新的线程去加载因为是异步的,
2. 并且继续解析文档(HTML、css)不阻碍文档的解析,
3. 对于 async 属性脚本,脚本加载完之后立即执行,defer 要等到文档解析完才执行
这块有一个禁止,
异步加载的 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 里面 document.write 输出 '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对象
第二点:文档解析完了
第三点:文档解析完并加载完了,并且执行完了
主要就是这个步骤,
围绕着这三个点来进行讨论的,在这三点里面穿插点东西
第 一 点
先创建小写的 document 对象,这时候 document.readyState = 'loading'
第 二 点
第 2 步到第 4 部是一些正常情况,
当遇到 link 标签怎么解析(第 2 步),
当遇到 script 标签怎么解析(第 3 步),
当遇到 异步的script标签 怎么去解析(第 4 步),讨论的是这些事
然后这些事都完事之后,可能还会遇到 img 等标签带 src 属性的(第 5 步),
遇到有 src 的属性的标签,整个页面会开启一个异步的线程,加载 src 里面的内容,
并且会同时会继续解析下面的文档。
第 三 点
文档解析完成,
等到这些该解析解析完了之后,到了一个节点叫 ,当全部解析完毕 DOM 树构建完,DOM 树构建完了之后(第 6 步)
1. 第一个关键是 document.readyState = 'interactive'(第 6 步)
2. 然后第二个标准性的事件是当文档解析完之后,所有 defer 加载的脚本会按照他们的出生顺序去执行,
但是 defer 脚本一定会等到文档解析完毕之后才会执行(第 7 步)
3. 第 8 条当文档解析完毕之后,document 还会触发一个事件叫 DOMContentLoaded 事件,
触发完这个事件之后,也就标准着从一个时代跨国另一个时代,程序从正常同步脚本执行阶段转会为异步事件驱动阶段(第 8 步)
4. 第 9 步,当 async 脚本加载并执行完毕、img 等标签加载完毕之后,也就是说所有东西全部都加载并执行完毕之后
这时候触发 document.readyState = 'complete',同时 widonw 对象触发 load 事件(第 9 步)
这是整体的时间线,其实整体就三点
1. 创建 document对象
2. 文档解析
3. 然后执行并加载完
四、验证时间线的过程
围绕 document.readyState 来看一下时间线的过程,
document.readyState = loading 一直在变 loading -> interactive -> complete
写一个 document.readyState 打印出的是什么?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> console.log(document.readyState); // loading </script> </body> </html>
什么时候变成 interactive?
1.当文档解析完变成 interactive,
文档解析完要形成一个 dom 树,生成 dom 树至少要把最后一个 dom 结构解析完,
2. 而最后一个 dom 结构又是 script 标签自己,script 标签也是 dom 节点(script 标签都能生成),
所以 script 自己解析完成之后,才能让 document.readyState 变成 interactive
3. 但是在 script 里面 dom 树还没有完全构建完毕,所以现在打印完的还是最开始的 loading
就想看 interactive 怎么办?在下面再写一个 script 标签
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</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 标签(这个方法不行我也这么简单的想过)
还有 onload 事件,
用 window.onload 之后,打印结果是变了,变的是 complete
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document</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>document</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>
当 onreadystatechange 变的时候就把 document.readyState 打印出来,结果是 interactive 和 complete
为什么没有 loading?
初始的时候就是 loading,而事件 onreadystatechange 一触发 readyState 就变值了
所以还要把 document.readyState 单独打印
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document</title> </head> <body> <div style="width:100px;height:100px;background-color:red;"></div> <script> console.log(document.readyState); // loading(把document.readyState单独打印) document.onreadystatechange = function(){ console.log(document.readyState); // interactive compltate } </script> </body> </html>
这回三个状态都出来了
这里面还有些事件
1. window.onload 事件我们知道,
2. document 上还有一个 DOMContentLoaded 事件,也是在文档解析完出现的
试试 onDOMContentLoaded 事件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>document</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>document</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 标签写在 HTML文档最下面是最合适的情况?
script 一般是要操作 dom 的,写在最下面最后才执行,意味着上面的 dom 已经处理完事了,所以写到最后的位置是最科学的。
但是又不是那么很科学,script 标签写到最后说明前面的 dom 结构全完事了,
但是在处理的时候其实没有完全等到 dom 树全部都构建完才去处理,
因为最后一步是 script 标签,但是 script 标签是不会处理自己,所以 script 标签写到最后也就是投机取巧。
其实最好的方式有点类似于 window.onload,等页面全部处理完后再处理,
但 window.onload 是土鳖写法,因为要等图片全下载完才触发,全部下载完成是给用户看的,
我们不需要全部下载完成,只要 dom 树构建完成能操作了就行
其实最好方法是等 dom树解析完成 然后在操作,
有一种方法监听 dom 解析完,然后再执行 js,DOMContentLoaded 就是这样的方法。
jQuery 的 $(document).ready(function(){ }) 当 dom 解析完就执行
面试题经常会问到 jQuery 的 $(function(){}) 和 window.onload 的区别
1. window.onlaod 要完全加载完才执行,比较慢
2. jQuery 的 $(function(){}) 要等到解析完才执行,更好一些
$(document).ready(function(){ // 里面写当dom解析完就执行的部分 });
jQuery 的 $(function(){}) 底层原理就是依据,
document.readyState 变成 interactive 和 DOMContentLoaded 事件
document.addEventListener('DOMContentLoaded', function(){ console.log('a'); }, false);
模拟 jQuery 把 script 标签写在上面,也可以选出下面的 div
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>DOMContentLoaded</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,优化一些操作,提供一个很坚实的理论基础。