事件循环

2022-08-01 JavaScriptJS习题

# 事件循环

在 JavaScript 中,事件循环(Event Loop)是其异步编程模型的核心机制。

  • 基本结构
    • 调用栈(Call Stack):当函数被调用时,会被压入调用栈。当函数执行完毕后,会从调用栈中弹出。
    • 任务队列(Task Queue):也称为宏任务队列(macrotask queue),存放着宏任务,如 setTimeout、setInterval、I/O 操作等。
    • 微任务队列(Microtask Queue):存放着微任务,如 Promise 的回调函数、MutationObserver 等。
  • 工作流程
    • JavaScript 引擎首先执行同步代码,这些代码会依次被压入调用栈并执行。
    • 当遇到异步任务时,JavaScript 引擎会将其交给浏览器的其他模块(如定时器模块、网络请求模块等)去处理,并继续执行后续的同步代码。
    • 当异步任务完成后,会将对应的回调函数放入任务队列中。
    • 当调用栈为空时,JavaScript 引擎会检查任务队列。如果任务队列中有宏任务,会取出一个宏任务并执行。
    • 宏任务执行完毕后,会检查微任务队列。如果微任务队列中有任务,会依次执行微任务队列中的所有任务,直到微任务队列为空。
    • 然后,继续从任务队列中取出下一个宏任务进行执行,如此循环往复。
  • 示例
console.log('同步代码开始执行'); 

setTimeout(() => {

    console.log('setTimeout 回调执行');

}, 0); 

Promise.resolve().then(() => {

    console.log('Promise 微任务执行');

}); 

console.log('同步代码结束执行'); 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在这个例子中,首先会输出 “同步代码开始执行” 和 “同步代码结束执行”,然后执行微任务队列中的 “Promise 微任务执行”,最后执行宏任务队列中的 “setTimeout 回调执行”。

  • 作用和意义
    • 实现非阻塞编程:允许 JavaScript 在执行耗时的异步操作时,不会阻塞后续代码的执行,从而提高程序的响应性。
    • 更好地处理用户交互:在处理用户界面的交互事件时,能够及时响应用户操作,提供流畅的用户体验。
    • 优化性能:通过合理地安排异步任务的执行顺序,可以提高程序的性能和效率。

# 练习题1

# 输出是什么?

const first = () =>
  new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
      console.log(7);
      setTimeout(() => {
        console.log(5);
        resolve(6);
        console.log(p);
      }, 0);
      resolve(1);
    });

    resolve(2);
    p.then((arg) => {
      console.log(arg);
    });
  });
first().then((arg) => {
  console.log(arg);
});
console.log(4);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
答案

答案: 3,7,4,1,2,5,Promise{1}

//第一步
输出: 3, 7, 4

微任务 = [
    p.then((arg) => {
        console.log(arg);
    })

    first().then((arg) => {
        console.log(arg);
    })
]
宏任务 = [
    () => {
        console.log(5);
        resolve(6);
        console.log(p);
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//第二步

执行:
p.then((arg) => {
    console.log(arg);
})

输出: 3, 7, 4, 1

微任务 = [
    first().then((arg) => {
        console.log(arg);
    })

]
宏任务 = [
    () => {
        console.log(5);
        resolve(6);
        console.log(p);
    }

]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//第三步

执行:
first().then((arg) => {
    console.log(arg);
})

输出: 3, 7, 4, 1, 2

微任务 = []

宏任务 = [
    () => {
        console.log(5);
        resolve(6);
        console.log(p);
    }

]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//第四步

执行:
    () => {
        console.log(5);
        resolve(6);
        console.log(p);
    }

输出: 3, 7, 4, 1, 2, Promise

微任务 = []

宏任务 = []
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 练习题2

# 输出是什么?

let a;
let b = new Promise((resolve) => {
    console.log(1);
    setTimeout(() => {
        resolve();
    }, 1000);
}).then(() => {
    console.log(2);
})
a = new Promise(async (resolve) => {
    console.log(a);
    await b;
    console.log(a);
    console.log(3);
    await a;
    resolve(true);
    console.log(4);
})
console.log(5);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
答案

答案: 1, a(undefined) , 5, "等待一秒", 2, Promise{<pendding>} , 3

// 关于这种涉及 await 的题目,可以简单先做一下形转化,统一转换为 promise,这样便于理解
let a;
let b = new Promise((resolve) => {
    console.log(1);
    setTimeout(() => {
        resolve();
    }, 1000);
}).then(() => {
    console.log(2);
})
a = new Promise(async (resolve) => {
    console.log(a);
    b.then(() => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    })
})
console.log(5);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一步

输出: 1, a(undefined), 5

微任务 = [
    b.then(() => {
        console.log(2);
    })
    .then(() => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    })
]

宏任务 = [
    () => {
        Promise {
            b
        }.resolve(), 1000
    }

]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 第二步
//因为b.then在setTimeout中resolve, 所以这里优先执行

(() => {
    Promise {
        b
    }.resolve()
}, 1000);

输出: 1, a(undefined), 5, '等待一秒'

微任务 = [
    (() => {
        console.log(2);
    })
    .then(() => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    })
]

宏任务 = []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 第三步
执行:
    (() => {
        console.log(2);
    })
    .then(() => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    })

输出: 1, a(undefined), 5, '等待一秒', 2

微任务 = [
    () => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    }
]

宏任务 = []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 第四步
执行:
    () => {
        console.log(a);
        console.log(3);
        a.then(() => {
            resolve(true);
            console.log(4);
        })
    }

输出: 1, a(undefined), 5, '等待一秒', 2, `Promise{<pendding>}`, 3

微任务 = [
    a.then(() => {
        resolve(true);
        console.log(4);
    })
]

宏任务 = []

//结束: 因为 a.then 需要被 resolve 才能被执行, 而 resolve 又在 a.then 内, 因此 a.then 无法执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
更新时间: 2024/9/27 下午6:33:04
大鱼-钢琴版
赵海洋