Go to comments

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'

image.png

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>

image.png

1). window.onload是要等整个页面所有的东西,全加载并解析完后才执行的,

2). 这个时候在document.write('a'),

     那不好意思就他会实现一个非常强大的功能,把所有之前的文档流全部清空,

3). 然后打印出 document.write('a') 里面的东西 'a'


也就是说document.write在一定情况下能消除文档流的功能(什么是文档?写到html里面的东西全叫文档)


消除到一个什么程度呢?把自己的script标签都删了

image.png

在整个文档全部加载完之后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结构

image.png


问题天使:

在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

image.png


在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

image.png


为什么没有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>

这回三个状态都出来了

image.png


这里面还有些事件

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之后,相当于这两个并行发生的

image.png


说一个基本概念:

为什么要把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>

image.png


script标签写在上面更美观些,但是要能处理下面的代码,

而且这样效率更高,因为等dom都解析完才去执行,不用等到dom加载完才执行。


当然通用的做法还是把script标签写在最下面,因为实在是没有比这更快的方式了。


这就是js时间线

js时间线要背也背下来这个东西非常重,这个会为未来优化一些DOM,优化一些操作,提供一个很坚实的理论基础。


笔记ppt截图

QQ截图20200824185909.png



Leave a comment 0 Comments.

Leave a Reply

换一张