对JS异步编程的探究

在探究“异步“的时候,我们总会联想到多线程实现异步,那么为什么JavaScript非要弄成单线程,还要实现异步呢?

为什么JS是单线程的

为什么JavaScript是单线程的呢??因为JavaScript只是一个浏览器的脚本语言,和其他语言比如Java、c++不一样,可以这样理解:JavaScript是面向浏览器编程的语言,而Java、c++等是面向操作系统编程。
所以这就决定了JavaScript的主要功能是与用户交互以及操作DOM。既然是负责与用户交互,那么弄成多线程就可能会出现一些很复杂的同步问题,毕竟用户不是程序员,不知道用户会做出什么神奇操作。

JavaScript异步和同步有什么区别呢?为什么JS需要异步呢?

同步就是指所有的事情都需要一个个来,前一个没有解决,后一个就不能执行。而异步就比较吊了,它能同时干好多事情,不管你前边的有没有解决。
想象一下这个场景:你买了菜准备回家做饭,然后步骤是什么呢?

如果用同步的方法
首先要淘米
然后把米放进电饭锅
然后就在那等饭熟
然后再去炒菜
然后吃饭

如果采用异步的方法
首先淘米
然后把米放进电饭锅
然后我一边炒菜一边等饭熟
炒完菜看看饭熟了没,熟了直接开吃,没熟就等熟了开吃

可以看出来异步是节省了好多时间的。所以为了良好的用户体验,JavaScript需要异步机制

有人说你一边炒菜一边蒸米饭这不是多线程吗,这不是多线程,我要是左手炒菜右手淘米然后放进电饭锅(Java和python这类多线程语言是这样的)这是多线程,菜是我炒的,米饭不是我蒸的,是电饭锅蒸的,所以这不是多线程。

上面提到了Java和python的多线程,这类语言是这样的,左手炒菜右手淘米蒸米饭,这样很明显比JavaScript的单线程异步效率要高,但是更加“费脑”,也就是消耗性能比较多。

什么时候会用到异步呢?

我了解到的有三种情况(欢迎补充)
1.网络请求
2.定时器
3.事件绑定 (事件绑定阶段很快,基本没有多少时间开销)

任务队列和事件轮询

想要进一步了解JavaScript的异步,就必须先要知道任务队列和事件轮询机制
JS是单线程的,也就是说它同时只能处理一个任务,但是JavaScript的功能是什么,它的功能是对浏览器发出指令,操作浏览器,而浏览器是多线程的,浏览器在运行的时候会有多个线程。一般如下:

1.JavaScript引擎线程
作用:执行js任务

2.定时器触发线程
作用:处理定时器相关的操作(setTimeout和setInteval)

3.ui渲染线程
作用:渲染界面

4.http请求线程
作用:ajax相关操作

5.事件轮询线程
作用:轮询任务队列

6.浏览器操作线程
作用:处理用户对浏览器本身进行的操作

JavaScript操控浏览器,换句话说其实活都是浏览器干的

了解了上面这些后,下面开始讲任务队列和事件轮询

先来看看一段代码

setTimeout(() => {
  console.log(1)
 }, 0)

setTimeout(() => {
  console.log(2)
}, 1000)

console.log(3)

有的naive萌新的看了,嗯。。延迟0秒输出1,延迟1秒输出2,然后输出3,所以顺序是立刻输出1,然后1s后输出2,然后立刻输出3。

这是不对的

正确答案是立刻输出3,然后立刻输出1,然后1s后输出2

然后问题来了,你这不对呀,怎么直接就跳过前两个函数了?

因为setTimeout是异步函数,还记不记得上边那个做饭的例子,setTimeout相当于煮饭。如图,这三个函数位于JavaScript线程上,按顺序执行。

JavaScript任务分两种,同步任务和异步任务


image.png

当执行到第一个函数时候,发现这是一个异步任务,就会在主线程外开一个任务队列,把这个异步任务丢到任务队列中,交给浏览器执行,然后浏览器发现需要开一个定时器线程,就给他开一个定时器线程

执行到第二个函数时,发现这也是一个异步任务,就把它也丢到呢个任务队列中,又给它开了一个定时器线程

执行到第三个函数时候,发现这是一个同步任务,那就立刻执行,输出3


image.png

当输出3之后,主线程已经空了,于是就开始进行事件轮询,看看任务队列里的任务有木有完成的。

image.png

0秒后,定时器线程1完成,会返回给任务队列一个消息,告诉任务队列我这个任务已经完成了,事件轮询发现这个任务已经做好了,就把console.log(1)放到主线程上,主线程开始执行console.log(1)。


image.png

完成之后浏览器释放掉定时器线程1,取消任务队列中相应的任务,继续进行事件轮询


image.png

1s后,定时器线程2执行完毕,返回给任务队列一个消息,表示已经做好了,然后又把console.log(2)放到主线程上,开始执行console.log(2)


image.png

好了,过程就是这样

大部分人看到这肯定会有这样的疑惑,为什么setTimeout就是异步任务,console.log()就是同步任务?为什么,为什么?

这就要从浏览器的结构讲起!!

浏览器这个东西他有很多模块


image.png

其中JavaScript模块用来解析JavaScript代码,很强大,会console.log(),拼接字符串,赋值等等...但是,他不会定时!!!

有人说你这不搞笑呢,不会定时它这个setTimeout怎么执行的。它真滴不会定时,定时不是JavaScript引擎模块做的,是定时器模块做的,setTimeout函数只是告诉定时器模块,兄弟帮我定个时,好了叫我。

也就是说,当JavaScript主线程执行到setTimeout(() => {console.log(2)}, 1000)函数时候就知道这是个定时器,程序员想让我1秒后输出2,但是我不会计时呀,定时器模块会,所以JavaScript模块就找到定时器模块,叫他帮个忙,记个时,1s后叫我。我先去干其他事了。
定时器模块收到通知后,没问题兄弟,我就是专门计时的,交给我吧!
1s后,定时器模块告诉JavaScript模块,兄弟,1s到了。
然后JavaScript知道了,1s到了,我该执行console.log(2)了。

其实不光是计时,很多东西都是JavaScript委托给别人做的,比如网络请求,发送ajax,其实是委托给网络请求模块做的,简单来说,所有请别的模块做的任务,都是异步任务。

自己做的任务起码有点底是吧,委托别人的任务自己不知道什么时候做完,向网络请求,要是网络不好,能花几分钟几十分钟。这段时间不能干等这个请求对吧,还有一大堆事情要做呢,而且用户体验也不好对吧。

总结一下,就是:
JavaScript模块自己做的事情就是同步任务,
JavaScript模块请别的模块做的任务就是异步任务

目前我了解到的有三类的任务JavaScript模块需要找其他模块帮忙:
网络请求(找http网络模块帮忙)
定时任务(找定时器模块帮忙)
事件绑定(找事件绑定模块帮忙)

可以粗略的认为,除了以上三种任务,都是同步任务。

你可能感兴趣的:(对JS异步编程的探究)