进程与线程
进程:
首先程序的运行有自己的内存空间可以把这部分的内存空间简单的理解为进程
线程:
有了进程之后,我们就可以运行浏览的代码了
运行代码的【人】我们可以称之为【线程】
一个进程有多个线程,所以当进程开启时,就会创造一个线程来运行代码,这个线程叫做主线程
如果程序需要同时执行多个代码块,主线程就会开启更多的线程来执行代码,所以一个进程中可以包含多个线程
浏览器
浏览器的进程是多线程的,主要包括渲染线程,事件线程,js引擎线程,定时器线程,请求线程等
在这些线程中有一个极其重要的线程,叫做渲染主线程
渲染主线程
渲染主线程,这个线程所负责的任务多的离谱(如:js的执行,样式的计算,图层的处理,html的解析,css的解析.....)
渲染主线程只有一个,这也解释了为什么说js是单线程的原因(js运行在渲染主线程中,主线程是唯一的)
但是渲染主线程在单位事件内只能做一件事:我们可以吧渲染主线程比作人,你要不拉屎,要不吃饭。不能边拉边吃(千万别说自己的那点特殊癖好哈)
那么问题来了,主线程如何调度这些任务?
1.当我执行js时,用户点击了按钮,怎么办?是直接执行按钮的任务吗?还是接着执行js的任务
2.当我执行js时,定时器到达了时间怎么办?执行他的回调函数吗?
3.当我点击按钮的时候定时器恰好到了时间怎么办?
.....
正是因为有着这些问题,js为了解决这些问题和适应渲染主线程在单位事件内只能做一件事的这一规则,才诞生了js的运行机制,也就是常说的事件循环
换句话说如果浏览器是渲染主线程能做到边吃边拉,也就不会有事件循环,所谓的事件循环不过是浏览器渲染主线程的工作方式
消息队列
直接与渲染主线程沟通的媒介,你可以把他想象成私人管家由它来管理渲染主线程的吃喝拉撒(比如它规定了渲染主线程要吃饭前必须先洗手)
在过去,消息队列简单的被分为宏队列(task)和微队列(microtask)
但是随着浏览器漫长的发展,浏览器的环境也变的越来越复杂,取而代之的是一种更加灵活的处理方式,新的官方解释为:
在新的消息队列中可以用多种队列承载各种任务,但是同类型的任务必须在同类型的队列里。
其实对于前端来说并没有太多的影响,但是对于写浏览器的倒是清晰了不少
浏览器可以自行的调度队列其中的任务,但是必须有一个微队列,且优先级最高,优先调用
执行过程
具体的执行大体可以分为着几步
- 发现setimeOut等关键词将其放到计时的线程中去,当记时完成后将它的回调函数打包成任务放到消息队列里的特殊队列中
- 遇见 Promise.resolve 等函数将其打包成任务放到微队列中
- 如果微队列中有任务,拿出该任务先执行
- 微任务执行完毕在特定的队列中拿出该队列的任务执行
- 如果在执行过程中发现微任务,将其打包到微队列中在下一次事件循环中执行
上述皆是自己总结,有错误欢迎指正
现在用一道题来解释一下上述所语
Promise.resolve().then(function promise1 () {
console.log('promise1');
})
setTimeout(function setTimeout1 (){
console.log('setTimeout1')
Promise.resolve().then(function promise2 () {
console.log('promise2');
})
}, 100)
setTimeout(function setTimeout2 (){
console.log('setTimeout2')
}, 0)
console.log(1)
将所有的代码放到包装成任务放到主线程中执行 输出1
1.此时遇到了Promise.resolve().then,那么我们需要将它的回调函数打包成任务放到消息队列的微队列中
2.遇到了setTimeout,此时将它放到放到记时线程中记时
3.又遇到了setTimeout,此时将它放到放到记时线程中记时 此时的图应该时这样的
4.第二个setTimeout记时完毕将他的回调函数setTimeout2放到消息队列中的延时队列中.
5.第一个setTimeout记时完毕将他的回调函数setTimeout1放到消息队列中的延时队列中.
6.将微队列中的任务拿出放到主线程中执行,输出promise1
7.将setTimeout2拿出来放到主线程中执行,输出setTimeout2
8.将setTimeout1拿出来放到主线程中执行,输出setTimeout1
9.遇到 promise2拿出来放到消息队列的微队列中
10.执行微任务,输出promise2
大概就是这些,写的不好多担待