JS的异步加载和时间线

JS的异步加载

页面的渲染过程

DOMTree + CSSTree = renderTree
根据HTML结构生成DOM Tree
根据CSS生成CSSTree
根据DOM和CSSTree 整合形成RenderTree
根据RenderTree开始渲染和展示
遇到<script>时,会执行并阻塞渲染 因为js有权利改变DOM结构,避免冲突从上到下执行
布局(Layout)和绘制(Paint),重绘(repaint)和重排(reflow/回流)
  1)重绘:根据元素的新属性重新绘制,使元素呈现新的外观
  2)重排:当渲染树中的一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建(效率极低)
  3)重排必定会引发重绘,但重绘不一定会引发重排

什么是异步

提到异步就不得不提另外一个概念:同步。那什么又叫同步呢。它是指同一时间只能做一件事,也就是说一件事情做完了才能做另外一件事,在程序中,我们都知道代码的执行是一行接着一行的,比如下面这段代码:

   let ary = [];
	for(let i = 0;i < 100;i++){
	    ary[i] = i;
	}
	console.log(ary);
	

这段代码的执行就是从上往下依次执行,循环没执行完,输出的代码就不会执行,这就是典型的同步。在程序中,绝大多数代码都是同步的。简单说就是加载到js文件的是会等待js文件加载完成且执行完成 再继续执行后面的代码(html css js), 同步操作的优点在于做任何事情都是依次执行,井然有序,如果代码不是同步执行,有些代码需要依赖前面代码执行后的结果,但现在大家都是同时执行,那结果就不一定能获取到。而且这些代码可能在对同一数据就进行操作,也会让这个数据的值出现不确定的情况。
当然同步也有它的缺点。由于是依次进行,假如其中某一个步骤花的时间比较长,那么后续动作就会等待它的完成,从而影响效率。这时候就用到异步。

异步的实现
多线程

线程可以理解成一个应用程序中的执行任务,每个应用程序至少会有一个线程,它被称为主线程。如果你想实现异步处理,就可以通过开启多个线程,这些线程可以同时执行。这是异步实现的一种方式。不过这种方式还是属于阻塞式的。什么叫做阻塞式呢。你想想,开10个窗口可以满足10个人同时买票。但是现在有100个人呢?不可能再开90个窗口吧,所以每个窗口实际上还是需要排队。也就是说虽然我可以通过开启多个线程来同时执行很多任务,但是每个任务中的代码仍然是同步的。当某个任务的代码执行时间过长,也只会影响到当前线程的代码,而其他线程的代码不会受到影响。

单线程非阻塞式

假设现在火车站不想开那么多窗口,还是只有1个窗口提供服务,那如何能够提高购票效率呢?我们可以这样做,把购票的流程分为两步,第一步:预定及付款。第二步:取票。其中,第一步可以让购票者在网上操作。第二步到火车站的窗口取票。这样,最耗时的工作已经提前完成,不需要排队。到火车站时,虽然只有1个窗口,1次也只能接待1个人,但是取票的动作很快,平均每个人耗时不到1分钟,10个人也就不到10分钟就可以处理完成。这样既提高了效率,又少开了窗口。这也是一种异步的实现。我们可以看到,开1个窗口,就相当于只有1个线程。然后把耗时的一些操作分成两部分,先把快速能做完的事情做了,这样保证它不会阻塞其他代码的运行。剩下耗时的部分再单独执行。这就是单线程阻塞式的异步实现机制。

JS中的异步实现

我们知道JS引擎就是以单线程的机制来运行代码。那么在JS代码中想要实现异步就只有采用单线程非阻塞式的方式。比如下面这段代码:

console.log("start");
setTimeout(function(){
    console.log("timeout");
},5000);
console.log("end");

这段代码先输出一个字符串”start”,然后用时间延迟函数,等到5000秒钟后输出”timeout”,在代码的最后输出”end”。最后的执行结果是:

start
end
//等待5秒后
timeout

从结果可以看到end的输出并没有等待时间函数执行完,实际上setTimeout就是异步的实现。代码的执行流程是这样的:
首先执行输出字符串”start”,然后开始执行setTimeout函数。由于它是一个异步操作,所以它会被分为两部分来执行,先调用setTimeout方法,然后把要执行的函数放到一个队列中。代码继续往下执行,当把所有的代码都执行完后,放到队列中的函数才会被执行。这样,所有异步执行的函数都不会阻塞其他代码的执行。虽然,这些代码都不是同时执行,但是由于任何代码都不会被阻塞,所以执行效率会很快。

当setTimeout执行后,什么时候开始计时的呢?由于单线程的原因,不可能在setTimeout后就开始执行,因为一个线程同一时间只能做一件事情。执行后续代码的同时就不可能又去计时。那么只可能是在所有代码执行完后才开始计时,然后5秒后执行队列中的回调函数,是这样吗?我们用一段代码来验证下:

console.log("start");
setTimeout(function(){
    console.log("timeout");
},5000);
for(let i = 0;i <= 500000;i++){
    console.log("i:",i);
}
console.log("end");

这段代码在之前的基础上加了一个循环,循环次数为50万次,然后每次输出i的值。这段循环是比较耗时的,从实际运行来看,大概需要14秒左右(具体时间可自行测算)。这个时间已经远远大于setTimeout的等待时间。按照之前的说法,应该先把所有同步的代码执行完,然后再执行异步的回调方法,结果应该是:

start
i:1 
(...) //一直输出到500000
//耗时14秒左右
end
//等待5秒后
timeout

但实际的运行结果是:

start
i:1 
(...) //一直输出到500000
//耗时14秒左右
end
//没有等待
timeout

从结果可以看到setTimeout的计时应该是早就开始了,但是JS是单线程运行,那谁在计时呢?JS的单线程并不是指整个JS引擎只有1个线程。它是指运行代码只有1个线程,但是它还有其他线程来执行其他任务。比如时间函数的计时、AJAX技术中的和后台交互等操作。所以,实际情况应该是:JS引擎中执行代码的线程开始运行代码,当执行到异步方法时,把异步的回调方法放入到队列中,然后由专门计时的线程开始计时。代码线程继续运行。如果计时的时间已到,那么它会通知代码线程来执行队列中对应的回调函数。当然,前提是代码线程已经把同步代码执行完后。否则需要继续等待,就像这个例子中一样。

异步加载js 按需加载,用到的时候再加载,不用到不加载
三种方案
1、IE专用(IE9以下) defer
代码执行时间:当整个网页解析完,执行代码(在网页而加载完之前)
同步加载

 <script src="load.js"></script> 
        //不影响html和css的加载  异步加载
<script src="load.js" defer></script> 
<script defer>
	 var a = 10;
	 console.log(a);
 </script>

2、async 加载完就执行,只能加载外部脚本 W3C的标准

<script src="load.js"  async></script>  
<script async> 
     // 高版本的浏览器中可以这么些,但是标准中不不允许的
     var b = 12;
     console.log(b);
</script>

3、创建一个script标签,插入到DOM中,加载完毕后callback 兼容性最好的

function loadScript(url,callback){
 var script = document.createElement('script');
 script.type = "text/javascript";
 if(script.readyState){
  // 状态码 readyState-->complete loaded 表示ie中script加载完成了
  script.onreadystatechange = function(){ //IE
   if(script.readyState == 'complete' || script.readyState == 'loaded'){
    callback();
   }
  }
 }else{
  script.onload = function(){ //加载完成的标志
   callback();
  }
 }
 script.src = url;//下载了指定地址的js文件
 document.head.appendChild(script);//挂到DOM树上,此时才执行了js文件中的代码
}

前端使用异步的场景:
1.定时任务:setTimeout,setInverval
2.网络请求:ajax 请求,动态、<img>加载
3.事件绑定
同步和异步的区别:
1.同步会阻塞代码执行,而异步不会
2.alert 是同步,setTimeout 是异步

参考:
https://zhuanlan.zhihu.com/p/74637286

js加载时间线

1、创建Document对象,开始解析web页面,解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中,这个阶段document.readyState = ‘loading’. 准备进入加载阶段。
2、遇到link外部css,创建线程加载,并继续解析文档。
3、遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。
-解析的同步js文件-
4、遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。对于async属性的脚本,脚本加载完成后立即执行。
-异步禁止使用document.write(),因为document.write()有可能会清空文档流 chrome浏览器禁止了外部异步的js文件使用document.write()方法-
5、遇到img等,先正常解析dom结果,然后浏览器异步加载src(图片资源),并继续解析文档。
6、当文档解析完成,document.readyState = ‘interactive’.准备进入交互阶段。
7、文档解析完成后,所有设置有defer的脚本按照顺序执行。
8、document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转换为事件驱动阶段。(注意,这里仍然可能会有异步脚本未完成加载。)
-事件驱动阶段:浏览器正常发挥功能-
9、当所有async的脚本的加载完成并执行后、img等加载完成后,document.readyState = ‘complete’,文档、脚本、资源此时全部完成 window对象触发load事件。
-页面所有的内容解析完成 加载完成-
10、从此,以异步响应式处理用户输入、网络事件等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值