栈的输出_基础面试27:什么是栈和队列?什么是单线程和异步?什么是事件环

栈和队列

在计算机内存中存取数据,基本的数据结构分为队列

(Stack)是一种后进先出的数据结构,有时候也管栈叫做“堆栈”,但是“堆”又是另一种复杂的数据结构,和栈完全是两码事。栈的特点是操作只在一端进行,一般来说,栈的操作只有两种:进栈出栈。第一个进栈的数据总是最后一个才出来。

84c177e942cacee5814535ba18f97ced.png

队列(Queue)和栈类似,但它是先进先出的数据结构,插入数据的操作从队列的一端进行,而删除的操作在另一端。

3bccbf4fe0f47f038bde3f72e495e6bb.png
e3eb764b42b6d34c88db4331e70e4e81.gif

在JavaScript中,函数的执行就一个典型的入栈与出栈的过程:

function fun1() {  function fun2() {  function fun3() {  console.log('do it'); } fun3(); } fun2();}fun1();
26f028d28b0c2be8d4a8740bd6b6f159.png

在程序执行时,首先将fun1,fun2,fun3依次入栈,而在调用函数时,是先将fun3调用(出栈)。

单线程和异步

在javascript这门语言中程序是单线程的,只有一个主线程,这是为什么?最初javascript的设计是跑在浏览器中的脚本语言,如果设计成多线程,两个线程同时修改DOM那以谁的为准呢?所以javascript为单线程,在一个线程中代码会一句一句向下走,直到程序跑完,若中间有较为费时的操作,那也只能等着。

单线程的设计使得语言的执行效率很差,为了利用多核心CPU的性能,javascript语言支持异步代码,当有较为费时的操作时,可将任务写为异步执行,当一个异步任务还没有执行完时,主线程会将异步任务挂起,继续执行后面的同步代码,之后再回过头来看,如果有异步任务运行完了再执行它。

这种执行代码的方式其实很符合我们生活中的很多场景,比如小明同学下班回家了,他很渴,想烧水泡茶,如果是同步的执行方式那就是烧水,在水没开时小明像个傻子似的等着,等水开了再泡茶;若是异步执行,小明先开始烧水,然后就去干点别的事,比如看会电视、听听音乐,等水烧开了再去泡茶。明显第二种异步方式效率更高。

常见的异步操作都有哪些?有很多,我们可以罗列几个常见的:

Ajax

DOM的事件操作

setTimeout

Promise的then方法

Node的读取文件

我们先来看一段代码:

2c3be74d8ed847706ac948365ec42280.png

因为setTimeout函数延时了1000毫秒执行,因此先输出1和3,而2是过了1000毫秒之后再输出,这很合逻辑。

我们稍稍改动一下代码,将setTimeout的延时时间改为0:

4da2e1835d6b1c0195adb73bb5aae017.png

为什么延时了0毫秒还是最后输出的2?再来看一段代码:

6846504e7fef7460014f87146c7d6595.png

以上三段代码,如果你能正确的写出结果,并且能说明白为什么这样输出,说明你对javascript的事件环理解的很清楚,如果讲不出来,我们就一起聊聊这里面发生了什么,其实很有意思。

javascript是怎么执行的?

一开始先简单聊了聊基本的数据结构,它和我们现在说的事件环有什么关系么?当然有,首先要明确的一点是:javascript代码的执行全都在栈里,不论是同步代码还是异步代码,这个一定要清楚。

fd7e6cafa42b559ae2e6b74585bd9c5e.png

而代码我们大体上分为了同步代码和异步代码,其实异步代码还可以再分为两类:宏任务和微任务。先这么认为:宏,即是宏观的、大的;微即微观的、小的。

javascript是解释型语言,它的执行过程是这样的:

  1. 从上到下依次解释每一条js语句
  2. 若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列
  3. 开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时到栈底了,栈清空了。
  4. 回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中
  5. 再返回第3步,直到异步队列都清空,程序运行结束

看图:

058dc254b65ef4165501873f9ca52973.png

通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。

明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。

浏览器中的事件环

聊了这么多,没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?

注意!微任务和宏任务的执行方式在浏览器Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。

在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。

最后来一个大考,以下代码的运行结果是什么:

ad2dae4b643f0977b2617aeeec795186.png

Chrome跑一下:

5f05a2e383efad762556d90997618061.png
今天到这里,欢迎关注
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值