这一次,彻底解决面试中【看代码说结果】的问题!

开发 前端
由于 JavaScript 是单线程的,它就像一家只有一个窗口的银行,客户需要一一排队来处理交易。同样,JavaScript 任务也需要按顺序执行,一个接一个。如果一项任务花费太长时间,则下一项任务必须等待。

Hello,大家好,我是 Sunday。

【看代码说结果】一直是前端面试中的常见问题。最近在陪几个同学面试过程中,几乎每个中、大厂的面试都会遇到一个或几个这样的问题。

图片图片

陪同学面试中遇到的真题

虽然这样的问题如此高频,但是能够回答好的同学却寥寥无几。

每次事后跟同学沟通,得到的结果都是:“实际开发中没有这么写的,NND 奇葩面试题!” 大家是不是也会有相同的感受呢?

是的!实际开发中我们肯定不会写出面试题里的凌乱场景。但是,我们不要忘记,学习的目的是:为了拿到更高薪资的 offer!,所以对很多同学而言 面试 比 实际开发 更重要! 只有很好的解决了 面试 的问题,大家才可以拿到满意的 offer。

所以,解决【看代码说结果】的问题就变得至关重要了。那么咱们今天,就好好地来聊聊 JS 中的执行机制问题,帮大家彻底理解 JS 的执行逻辑!

关于 JavaScript 线程基础逻辑

JavaScript 是一种单线程语言。

虽然最新的 HTML5 中引入了 Web Worker,但 JavaScript 单线程的核心保持不变。

因此,JavaScript中所有的“多线程”都是用单线程模拟的,JavaScript中的所有多线程都是骗人的!

JavaScript 事件循环

由于 JavaScript 是单线程的,它就像一家只有一个窗口的银行,客户需要一一排队来处理交易。

同样,JavaScript 任务也需要按顺序执行,一个接一个。如果一项任务花费太长时间,则下一项任务必须等待。

那么问题就来了:如果我们想浏览新闻,但新闻中的高清图片加载缓慢,我们的网页是否要一直卡住,直到图片完全显示出来?

因此,JS将任务分为两类:

  • 同步任务
  • 异步任务

当我们打开一个网站时,网页的渲染过程由一堆同步任务组成,例如:骨架屏幕、页面元素

消耗大量资源且需要很长时间才能完成的任务(例如:加载图像、音乐文件)则是异步的

图片图片

  • 同步和异步任务进入不同的执行“地方”,同步任务进入主线程,异步任务进入事件表并注册函数。
  • 当指定的任务完成时,事件表会将这个函数移动到事件队列中。
  • 主线程内的任务完成后,会从Event Queue中读取相应的函数并在主线程中执行。
  • 上述过程会不断重复,通常称为事件循环。

那么 JS 是如何知道主线程为空的呢?

在 JavaScript 引擎有一个监控进程,不断检查主线程执行栈是否为空。一旦为空,它就会去事件队列检查是否有任何函数正在等待调用。

如下面的代码所示:

let data = [];
$.ajax({
    url:www.lgdsunday.club,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行完成');

上面是一个简单的ajax请求代码:

  • ajax进入事件表并注册回调函数success。
  • 执行console. log(‘发送成功!’)。
  • ajax事件完成,回调函数成功进入事件队列。
  • 主线程success从事件队列中读取并执行回调函数。

通过上面的文字和代码,大家应该对JavaScript中的执行顺序有了初步的了解了吧。

那么接下来咱们来看一个 扰乱执行顺序的 “元凶” setTimeout

万恶的 setTimeout

setTimeout 可以延迟执行代码,比如:

setTimeout(() => {
    task();
},3000)
console.log('一个普通的打印');

根据我们之前的结论,setTimeout是异步的。所以,同步任务console.log应该先执行。因此,我们的结论是:

// 一个普通的打印
// task()

但是,这里我们要注意 3000 毫秒并不是 task 的执行时间,而是 task 进入任务队列(主线程)的时间

  • 3秒后,计时事件timeout完成。
  • task()进入任务队列(主线程)

即面试中常提到的 setTimeout 定时并不准确的问题 字节面试:如何实现准时的setTimeout 可以看下之前的这篇文章

那么同样的道理,在面试中常见的 setTimeout(fn, 0) 的延迟 0 毫秒 是什么意思呢?

setTimeout(fn ,0)是指定当堆栈中的所有同步任务完成且堆栈变空时,应在主线程上最早可用的空闲时间执行某个任务,而不需要等待任何额外的秒数。

所以,setTimeout(fn, 0) 并不会立刻执行

宏任务与微任务

宏任务与微任务的概念在这种题目中也是必须要掌握的。

  • 宏任务:包括整体脚本代码、setTimeout、setInterval
  • 微任务:Promise、process.nextTick

事件循环中事件的顺序决定了JavaScript代码的执行顺序。

  • 输入整个脚本(宏任务)后,它开始第一个循环
  • 然后它执行所有微任务。接下来,又从宏任务开始,直到一个任务队列完成后,才再次执行所有的微任务

我们通过一段代码来看下这个问题:

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');
  • 这段代码作为宏任务,进入主线程。
  • 当遇到 setTimeout 时,其回调函数被注册并调度到宏任务事件队列中。(注册流程同上,下文不再赘述)
  • 接下来,当遇到 Promise 时,new Promise立即执行,并将该then函数分派到微任务事件队列中。
  • 当遇到 console.log() 时,立即执行。
  • 在将整个脚本作为第一个宏任务执行之后。我们发现它 then 位于微任务事件队列中并被执行。
  • 第一轮事件循环结束
  • 第二轮循环开始;当然是从宏任务Event Queue开始。队列中对应setTimeout的回调函数立即被执行
  • 结尾
责任编辑:武晓燕 来源: 程序员Sunday
相关推荐

2024-05-20 00:00:00

代码主线程

2024-03-11 08:47:30

CRDT数据类型协同编辑

2019-09-12 09:40:34

秒杀系统高并发

2019-11-08 16:05:54

Promise前端链式调用

2024-05-15 10:14:00

CRDT数据类型协同编辑

2021-07-03 08:59:49

动态代理JDK

2018-08-07 14:45:52

编程语言JavaScripthtml

2020-09-28 14:41:24

Event Loop

2021-08-29 08:14:30

GPU CSS gpu

2019-06-05 13:00:00

2020-08-13 07:04:45

跨域CORS浏览器

2019-04-12 11:25:24

华为

2018-07-23 16:13:27

Google欧盟Android

2016-03-31 17:01:26

桂林甲天下

2021-03-11 12:15:37

Kubernetes云原生容器

2016-01-06 11:15:03

VR

2014-07-18 17:14:16

小米苹果雷军

2016-11-08 07:58:02

乐视难关科技新闻早报

2021-04-28 09:55:52

JavaLock接口并发编程

2019-11-05 11:17:11

Java虚拟机技术Java 堆
点赞
收藏

51CTO技术栈公众号