js面试题--异步和单线程

本文详细介绍了JavaScript的单线程特性、异步编程的原因及常见模式,如回调函数、Promise和async/await。重点讨论了Promise的状态转换以及如何使用Promise加载资源,还解释了事件循环(Event Loop)的工作原理,包括宏任务和微任务的执行顺序。最后,阐述了async/await如何简化异步代码,以及它们与Promise的关系和执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、单线程

  • js 是单线程语言,只能同时做一件事。
  • js 和 DOM 渲染共用同一个线程,因为 js 可能会修改 DOM 结构。

二、异步

  • 为什么要使用异步:程序在遇到等待(网络请求,定时任务)时,不能卡住。
  • 常见的异步模式有:回调函数、promise、async/await等。

详细说明链接:什么叫异步-优快云博客

三、同步和异步的区别

  •  异步不会阻塞代码执行。
  • 同步会阻塞代码执行。

四、异步的应用场景

  • 网络请求,如 ajax 图片加载。

  • 定时任务,如 setTimeout。

五、Promise

1. Promise是什么

  • Promise 是异步操作的一种解决方案。
  • Promise把 callback 嵌套的形式,变成了串联的形式。
  • 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题。

2. Promise 的三种状态

1. 三种状态

  • Promise 有 3 种状态,一开始是 pending(未完成)。

  • 执行 resolve,变成 fulfilled(resolved),已成功。

  • 执行 reject,变成 rejected,已失败。

2. 状态的表现

  • pending 状态,不会触发 then 和 catch。
  • fulfilled(resolved)状态,会触发后续的 then 回调函数。
  • rejected 状态,会触发后续的 catch 回调函数。

3. then 和 catch 改变状态

  • then 正常返回 fulfilled(resolved),里面有报错则返回 rejected。
  • catch 正常返回 fulfilled(resolved),里面有报错则返回 rejected。

4. then 和 catch 的链式调用题目

  • Promise 的实例化为同步部分会立即执行,后面的 then 和 catch 才进行异步处理

3. 使用 Promise 加载一张图片

// 异步加载图片。
function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

详细说明链接:Promise_小豪boy的博客

六、event loop(事件循环/事件轮询)

1. event loop是什么?

  • js 是单线程运行的。
  • 异步(setTimeout、ajax等)使用回调,基于 event loop。
  • DOM 事件也使用回调,基于 event loop。
  • event loop 就是异步回调的实现原理。

2. js 如何执行?

  • 从前到后,一行一行执行。
  • 如果某一行执行报错,则停止下面代码的执行。
  • 先把同步代码执行完,再执行异步。

3. event loop 的过程

  • 以下面代码为示例讲解:

先介绍几个概念:

  • Call Stack:调用栈,它里面是一个个待执行的函数。
  • Web APIs:浏览器提供的一套操作浏览器功能页面元素API。
  • Callback Queue:回调函数队列,它里面是一个个等待被执行的回调函数。

1. 初始状态

2. 执行第一行代码

  • 代码推入调用栈,执行代码,输出结果,清空调用栈。

3. 执行第二行代码

  • 代码推入调用栈,执行代码(但是setTimeout是浏览器定义的,执行代码相当于把里面的参数传给了Web APIs中的定时器timer里面,让浏览器5秒钟后将回调函数推入Callback Queue里面),执行完成清空调用栈。

4. 执行最后一行代码

5. 同步代码执行完

  • 会启动 event loop 机制,会循环的向 Callback Queue 中去查找是否有异步的回调函数,有就将其推入到 Call Stack 中去执行,没有就继续循环。
  • 开始刚刚执行完代码还不足5秒钟,Callback Queue 中没有数据。

  • 等待时机,5秒中后定时器将回调函数推入 Callback Queue 。

  • 此时,event loop 会发现回调函数立即将其推入 Call Stack 中去执行,执行完成清空调用栈。

4. 小结

  1. 同步代码,一行一行放在 Call Stack 执行。
  2. 遇到异步,会先 “记录” 下,等待时机(定时、网络请求等)。
  3. 时机到了,就移动到 Callback Queue。
  4. 如 Call Stack 为空(即同步代码执行完)Event Loop 开始工作。
  5. 轮询查找 Callback Queue,如有则移动到 Call Stack 执行。
  6. 然后继续轮询查找(永动机一样)。

七、async/await

1. async/await是什么

  • 异步回调 callback hell。
  • Promise then catch 链式调用,但也是基于回调函数。
  • async/await 是同步语法,彻底消灭回调函数。
  • 本质上只是一个语法糖,具体实现还是得有异步,还是得基于 event loop。
  • async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。
  • await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用.

2. async/await 和 Promise 的关系

  • 执行 async 函数,返回的是 Promise 对象。
  • await相当于 Promise 的 then。
  • try- catch可捕获异常,代替了 Promise的 catch。

3. async/await 的执行流程

  • 只要遇到了 await,await 本身会立即执行,而 await 后面的代码都相当于放在 callback 里,进行异步处理。
async function async1() {
    console.log('async1 start')  // 2
    await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
    console.log('async1 end') // 5 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务)
}

async function async2() {
    console.log('async2')  // 3
}

console.log('script start')  // 1
async1()
console.log('script end')  // 4
async function async1() {
    console.log('async1 start')  // 2
    await async2()

    // 下面三行都是异步回调 callback 的内容
    console.log('async1 end') // 5
    await async3()

    // 下面一行是异步回调 callback 的内容
    console.log('async2 end') // 7
}

async function async2() {
    console.log('async2')  // 3
}

async function async3() {
    console.log('async3 ')  // 6
}

console.log('script start')  // 1
async1()
console.log('script end')  // 4

4. for  of 异步循环

  • for...of是ES6的标准,该方法遍历的是对象的属性所对应的值(value:键值)。
// 定时算乘法
function multi(num) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

async function test() {
    const nums = [1, 2, 3]
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res);
    }
}

test()

补充:for...of,for...in,forEach和map的区别-优快云博客

八、宏任务 macroTack 和微任务 microTask

1. 什么是宏任务,什么是微任务

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件。
  • 微任务:Promise,async/await。
  • 微任务执行时机比宏任务要早。

2. event loop 和 DOM 渲染

  • 每次Call Stack清空(即每次轮询结束),即同步任务执行完。
  • 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染。
  • 然后再去触发下一次 Event Loop。

3. 微任务和宏任务的区别

  • 宏任务:DOM渲染后触发,如 setTimeout。
  • 微任务:DOM渲染前触发,如 Promise。

4. 为何微任务执行更早

  • 微任务是 ES 语法规定的。
  • 宏任务是由浏览器规定的。
  • 在执行 Promise 时,因为 Promise 是 ES 规范 不是 W3C 规范,所以不会经过 Web APIs,会等待时机直接进入 micro task queue(微任务队列)。

执行步骤为:

  1. Call Stack清空
  2. 执行当前的微任务
  3. 尝试 DOM 渲染
  4. 触发 Event Loop

async function async1() {
    console.log('async1 start')      // 2
    await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行

    // 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务)
    console.log('async1 end')        // 6
}

async function async2() {
    console.log('async2')            // 3
}

console.log('script start')          // 1

setTimeout(function () { // 异步,宏任务
    console.log('setTimeout')        // 8
}, 0)

async1()

// 初始化 Promise 时,传入的函数会立刻被执行
new Promise(function (resolve) {
    // 返回 Promise 之后,即同步执行完成,then 是异步代码
    console.log('promise1')          // 4
    resolve()
}).then(function () { // 异步,微任务
    console.log('promise2')          // 7
})

console.log('script end')            // 5

// 同步代码执行完,(event loop - call stack 被清空)
// 执行微任务
// (尝试触发 DOM 渲染)
// 触发 Event Loop,执行宏任务
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值