js的同步与异步

​ 众所周知,js是一个单线程的语言,学过java、c之类的都知道,其他语言有个叫类继承的东西,就相当于开辟另个一个流水线,是多线程

​ 而javascript就像一条流水线,它无法开辟别的流水线,是一个单线程,也就是说js要么加工,要么包装,不能同时进行多个任务和流程

同步与异步

什么是同步,什么是异步

​ 举个例子,我一边吃饭一边看剧,这是同步还是异步?有人说我同时在做2件事情,那肯定是异步了,但这其实是同步。

​ 以js来说,同步就是在浏览器执行js代码的时候,将所有同步(也就是大部分的代码)的代码放到一个执行栈中当中,遇到异步代码就把异步代码放到任务队列中,这样就形成了异步操作,同步与异步的差别就在于这条流水线上各个流程的执行顺序不同

​ 这也是js被称为单线程的原因,相比于其他的多线程语言而言,js对于同步代码与异步代码,只能选择其中一个执行,而其他语言可以同时进行

setTimeout和setInterval

​ 在js中,最基础的异步就是setTimeout喝setInterval这两个函数,但是很少人知道这两个人其实是异步,简单介绍一下吧

两者都是定时器

function void(){};
setTimeout(void,1000);//隔1000毫秒后执行一次void,然后就不会在执行了
setInterval(void,1000);//每隔1000毫秒(1秒)执行一次void

同步与异步的例子

用官方一点的话来讲,当一个js被执行的时候,把同步代码全放到一个执行栈里面,异步代码放到一个任务队列中,当栈执行完毕后开始执行任务队列

​ 很多人不理解这一句话,这里分别来解释一下,举个例子来细说一下:

<script>
	function add(){
        console.log(2)
    }
    function add3(){
        console.log(5)
    }
    function add2(){
        console.log(4)
    }
    console.log(1)
    setTimeout(add,5000);
    console.log(3)
    setInterval(add2,1000)
    setTimeout(add3,1000)
 </script>

执行的结果是

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKKJi4xm-1649055329831)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220404114851860.png)]

​ 首先,同步的代码是console.log(1)和log(3),所以先输出了1,3,然后就是异步的地方了。

​ 异步三个代码,setTimeout、setInterval、setTimeout,当我们执行完同步代码(也就是输出1,3)开始执行异步代码对于第一个异步操作,因为我们设置的延迟时间为5秒(这个地方,虽然没有显示出来,但是这个异步操作已经开始执行,也就是说当代码执行到这一步的时候计时就已经开始),然后就是setInterval(add2,1000)也就是4,5,4,4,4,然后此时,设置的第一个延迟5秒到了,就输出一个2。

然后来解释下为什么说同步是栈,而异步是队列

​ 我们同步的入栈顺序是log(1),log(3),然后出栈就是1,3,输出也就是1,3

异步的入队顺序是,add,add2,add3,出来的时候也是add,add2,add3,但由于add的延迟原因,所以add2先输出

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行


补充:
如上所说,当我们的同步任务在执行栈中执行完毕了,再去执行任务队列中的。假设我现在A、C、D在执行栈中,B在执行队列中,我想让C执行完后去执行B然后在执行D怎么办呢?下面就介绍一下asyncawait

async/awiat的使用规则:

  • async 表示这是一个async函数, await只能用在async函数里面,不能单独使用
  • async 返回的是一个Promise对象,await就是等待这个promise的返回结果后,再继续执行
  • await 等待的是一个Promise对象,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值
    简单的来说就是给一个函数前加上了async后,它就变成了一个异步函数,而await只能在async中使用
function LongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}
 
async function test() {
    const v = await LongTime();
    console.log(v);
}
 test()

这里举的很简单的一个例子,按照我们前面所说的,test()方法里,console.log是同步,而v=LongTime是一个异步的函数调用,所以应该会先执行console.log(v),但这里用到了await。也就是说真正的执行顺序是,先执行await LongTime()(同步),即执行了异步方法LongTime,然后返回了一个promise对象,当LongTime执行完毕后在把值赋给V,然后执行console.log。

事件循环

​ 当执行异步任务的时候,异步任务中可能也包含同样的同步任务与异步任务,顺序还是一样的,就算任务队列中没有任务也会去执行,这个过程会无限循环下去,这就是事件循环

宏任务与微任务

宏任务:普通任务,正常执行。正常的异步任务都是宏任务,最常见的就是定时器(setInterval, setImmediate, setTimeout)、IO任务

微任务:优先于宏任务执行(但不会抢断)。微任务出现比较晚,queueMicrotask、Promise和async属于微任务(当然,async就是promise)

​ 我们可以看出来,微任务的优先级比宏任务高,一个任务结束后,事件循环会找到并执行全部微任务,然后再查找其他任务,那么,怎么分别宏任务和微任务呢?

分辨宏任务与微任务

​ 来看个例子

<script>
    console.log('aaa');

	setTimeout(() => console.log(111), 0); //异步任务
	queueMicrotask(() => console.log(222)); //异步任务

	console.log('bbb');

 </script>

PS:setTimeout(() => console.log(111), 0);这里的()=>就类似于定义一个函数然后调用

输出的结果是

aaa

bbb

222

111

执行顺序就像我们之前说的,先执行同步的,也就是aaa,bbb,然后在执行异步的,但是这里,setTimeout

在queueMicrotask前面,却还是先执行了queueMicrotask,就是因为queueMicrotask是微任务,而setTimeout是宏任务,也就是说在异步中,会先完成所有的微任务,然后再去执行宏任务

为什么会有宏任务与微任务

​ 每个代码的影响不一样,重要性也不一样,按照官方的设想就是,任务之间的不平等,有些任务对于用户而言更加重要,需要先执行,有些任务(类似定时器)晚点执行也没有什么问题

​ 宏任务和微任务相当于一种优先级队列的方式

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐