script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

时间:2023-03-09 19:53:31
script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

前端优化有一点就是优化js的执行时机,一般做法是将script放置于body的结束标签,以避免加载执行js 文件导致页面渲染阻塞的问题
这种做法确实能防止页面阻塞,但是在页面渲染完成之后才去加载js文件,有时候会显得js文件加载时间过长。
于是我们可以合理的使用script的属性defer,async

(一)分析defer,async的作用

(1)在不加defer,async的情况下
页面会预先渲染,如果遇到script 标签,他就会停止页面的继续渲染,去加载js文件,带文件加载完并执行结束之后,才会继续刚才暂停的页面渲染,
这意味着在head中加载的js文件如果有操作dom,或者获取dom节点信息的操作,可能会出错,因为DOMContentLoaded还没执行。
这也就是为什么需要将script文件放置在body结束标签之前的原因

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

(2)async
使用async之后会使得浏览器异步加载js文件,异步加载的意思是在加载js文件的过程中并不会阻塞页面的渲染,页面的渲染和js文件的加载是同时进行的
但是在js文件加载完之后,就会停止页面的渲染立即执行js文件,这个也是async与defer的区别。
如果在文件里面有相关dom操作可能会报错,因为html可能只是解析了一部分
因为带async的脚本一定会在load事件之前执行,可能会在DOMContentLoaded之前或之后执行,这个与加载脚本的时间长短有关系。

case 01

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

case 02

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

(3)defer
页面的渲染和js文件的加载是同时进行的,但是js文件加载完之后并不会立即执行而是需要在页面渲染完之后才去执行,但是带有defer的脚本(defer-script)是在DOMContentLoaded之前执行的

参考:https://html.spec.whatwg.org/multipage/scripting.html#ready-to-be-parser-executed

case 01

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

case 02

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

(二)区分load与DOMContentLoaded

DOMContentLoaded:
  当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。
  https://developer.mozilla.org/zh-CN/docs/Web/Events/DOMContentLoaded
load:
  当一个资源及其依赖资源已完成加载时,将触发load事件。
(三)HTML解析过程与DOMContentLoaded触发时机

1.在既没有CSS也没有JS的情况下,HTML文档的解析过程为

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

DOMContentLoaded事件的触发时机为:HTML解析为DOM之后。

2.有CSS无JS的情况下,HTML文档解析过程为:

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

这里与1.不同的地方在于,渲染树的生成是基于DOM和CSSOM的。但是触发DOMContentLoaded的时间依然是在HTML解析为DOM后,无论此时CSS解析为CSSOM的过程是否完成。

3.当有JS时,HTML文档解析过程为:

script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded

(四)JS单线程,浏览器多线程的问题

浏览器是多线程:
1.js引擎线程
  作用:JS内核,主要负责处理Javascript脚本程序,例如V8引擎。Javascript引擎线程理所当然是负责解析Javascript脚本,运行代码。
  js 是单线程(js引擎线程):
  js运作在浏览器中,是单线程的,js代码始终在一个线程上执行,因此任务分为同步(立即执行)异步任务(事件队列),
  web worker也只是允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。

2.GUI渲染线程
  作用:渲染页面(js可以操作dom,影响渲染,所以js引擎线程和UI线程是互斥的。js执行时会阻塞页面的渲染)
  当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
  GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行
  这也是为什么<script>标签要放到body标签的最底部。

3.浏览器事件触发线程
  作用:控制交互,响应用户
  当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理

4.http请求线程
  作用:ajax请求等
  在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状
  态变更事件放到 JavaScript引擎的处理队列中等待处理。在发起了一个异步请求时,http请求线程则负责去请求服务器,有了响应以后,
  事件触发线程再把回到函数放到事件队列当中。

5.定时触发器线程
  作用:setTimeout和setInteval

6.事件轮询处理线程
  作用:轮询消息队列,event loop

主线程执行的说明: 【js的运行机制】
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

参考链接:https://blog.csdn.net/github_34514750/article/details/76577663