根据网络课程记录的一些笔记,受益匪浅
单线程是异步产生的原因
事件循环是异步的实现方式
浏览器运行会启动:
- 浏览器进程
- 网络进程
- 渲染进程(一个标签页是一个渲染进程)
某进程崩溃后,互不影响
渲染进程
渲染进程启动后,会开启一个渲染主线程,主线程负责执行html,css,js代码
默认情况下,浏览器会为每一个标签页开启一个新的渲染进程,以保证标签页之间互不影响(未来可能会根据不同的操作系统,进行改进,以减少内存消耗,目前还是这样)
渲染主线程
渲染主线程工作很繁忙,任务包括但不限于:
- 解析html
- 解析css
- 计算样式
- 布局
- 处理图层
- 页面刷新渲染
- 执行全局js代码
- 执行事件处理函数
- 执行计时器回调函数
- ....
渲染主线程进入一个无限循环
事件循环
主线程从优先级最高的消息队列中拿到任务并执行完毕的过程就是一次事件循环
专业回答:
事件循环又叫消息循环,是浏览器渲染主线程的工作方式.
在chrome源码中,它开启一个无限循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在适合时候将任务加入到队列末尾即可.
过去把消息队列简单的分为微队列和宏队列,此说法目前已无法满足复杂的浏览器环境,取而代之的是一种更灵活多变的处理方式.
根据W3C官方的解释,每个不同的任务都有对应的类型,同类型的任务必须在同一个队列,不同的任务可以分属于不同的队列,不同的队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪个队列的任务.但是浏览器必须有一个微队列,执行优先级最高,必须优先调度执行
异步
代码在执行过程中,会遇到一些无法立即处理的任务:计时器,网络通信,用户操作需执行的任务
这些耗时的操作,比如网络请求,计时器等如果一直在主线程中运行,会导致主线程阻塞(就是卡死),所以这些耗时的请求会放到专门对应的线程中,比如计时线程 网络线程中;当计时结束或网络请求完毕后,会将回调任务放到消息队列中,等待主线程的调度运行
JS是一门单线程语言,因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个
主线程承担着诸多的工作,渲染页面,运行js都在这里
如果使用同步的方式,主线程极可能阻塞,从而导致消息队列中的其他任务无法执行
这样一方面导致主线程浪费时间,另一方面导致页面无法及时更新,用户看来就是卡死了
所以浏览器采用异步的方式来避免此种情况的发生,当主线程遇到计时器 网络 事件监听时,主线程会将任务交给对应的其他线程区处理,自己结束此任务再从消息队列中拿取其他任务执行,当其他线程完成后,会将回调函数包装成任务加入到消息队列末尾,等待主线程的调度执行
采用异步的方式,就能达到浏览器永不阻塞(任务的除外),从而最大限度的保证了单线程的流畅运行
js阻碍页面渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS阻碍渲染</title>
</head>
<body>
<div id="test">你好,世界!</div>
<button id="btn">更改上面</button>
<script>
// 死循环多少毫秒
function shui(num=1000){
let time1 = Date.now() + num
while(Date.now()<time1){
}
}
let div = document.getElementById("test")
let btn = document.getElementById("btn")
btn.addEventListener('click',function(){
//修改文字
div.innerText = "hello,world!"
shui(3000)
})
</script>
</body>
</html>
上面的代码,结果是3秒后文字才改变;但是明明是先修改的文字内容后死循环的,为什么呢?
因为修改文字后,渲染主线程还没有进行绘制(绘制任务会放进消息队列),死循环就进来了,死循环阻塞的主线程,之后才调取了队列中的绘制任务并执行
任务的优先级
任务没有优先级,但是消息队列有优先级
- 每个任务都有任务类型,同一个类型的任务必须在一个队列中,不同类型的任务可以分属于不同的队列.在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行
- 浏览器必须准备好一个微队列,微队列中的任务优先级最高
随着浏览器的复杂度急剧提升,W3C 不再使用宏队列的说法
目前chrome中,至少有以下队列:
- 延时队列,存放计时器到达后的回调任务;优先级[中]
- 交互队列,存放用户操作后产生的时间处理任务;优先级[高]
- 微队列,优先级[最高]
添加任务到微队列的主要方式:Promise 和 MutationObserver
例如
Promise.resolve().then(()=>{console.log("我是微队列任务")})
JS中的计时器能做到精确计时吗?
标签:浏览器,渲染,队列,主线,任务,循环,事件,优先级 From: https://blog.51cto.com/u_15668841/9611632不能
- js的计时器本质上是使用了操作系统的相关计时函数,而操作系统的计时函数本身就存在一定的偏差
- 按照W3C的标准,浏览器的计时功能,如果嵌套超过5层,即从第6层开始就会带有4毫秒的最少计时时间,这样在计时低于4毫秒时就又带了一些偏差
- 受事件循环的影响,计时器的回调任务只能在渲染主线程执行完优先级高于计时任务的其他任务后,才能调度执行计时任务,并不是到时后立即执行,所以又加上一些偏差