什么是js执行机制
JavaScript 的执行机制指的是 JavaScript 代码在运行时的工作方式和顺序。它涉及以下几个关键概念:
- 单线程:JavaScript 是一门单线程的编程语言,意味着它只有一个主线程用于执行代码。这意味着 JavaScript 中的代码是按顺序执行的,一次只能执行一个任务。
- 任务队列:JavaScript 通过任务队列来管理要执行的任务。任务队列中存放着各种类型的任务,包括同步任务和异步任务。
- 事件循环:JavaScript 的事件循环是一个持续运行的过程,它负责监视任务队列并选择下一个要执行的任务。事件循环不断地从任务队列中获取任务并将其交给主线程执行。
- 同步任务和异步任务:同步任务是按照顺序在主线程上执行的任务,执行一个任务时会阻塞后续任务的执行。异步任务是在主线程上注册并在将来某个时间点执行的任务,执行异步任务时不会阻塞后续任务的执行。
- 微任务和宏任务:JavaScript 中的任务可以分为微任务和宏任务。微任务是在当前任务执行完毕后立即执行的任务,它们使用微任务队列进行管理。而宏任务是在事件循环的下一轮中执行的任务,它们使用任务队列进行管理。
JavaScript 的执行顺序:
- 执行同步任务,将函数调用和变量分配到调用栈中按顺序执行。
- 遇到异步任务,如定时器、事件监听等,将其注册到任务队列中,并继续执行后续的同步代码。
- 当同步代码执行完毕后,主线程会检查微任务队列,依次执行队列中的微任务。
- 执行完微任务后,主线程会从任务队列中取出一个宏任务执行。
- 循环执行步骤 3 和步骤 4,直至任务队列和微任务队列都为空。
需要注意的是,JavaScript 中的异步任务通常是通过回调函数、Promise、async/await 等机制来处理。通过合理使用异步任务和任务队列,可以实现非阻塞的代码执行,提高代码的性能和响应能力。JavaScript 的执行机制主要涉及以下几个概念:调用栈、事件循环和任务队列。文字有点单调,看看下面的图理解理解
图片
让我们通过一个例子来解释这些概念。假设我们有以下代码:
console.log("Script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
Promise.resolve().then(function() {
console.log("Promise");
});
console.log("Script end");
这段代码的执行机制如下:
- 首先,开始执行代码,遇到第一行 console.log("Script start"),它会立即打印 "Script start"。
- 接下来,遇到 setTimeout,它是一个异步函数,会被放入任务队列中,并设置一个定时器。由于定时器时间为 0,所以不会立即执行。
- 然后,遇到 Promise.resolve().then(),它会创建一个 Promise 对象,并将 .then() 中的回调函数放入微任务队列中。
- 继续执行下一行,打印 "Script end"。
- 此时,主线程上的同步代码执行完毕,开始执行微任务队列中的任务。首先执行 Promise 的回调函数,打印 "Promise"。
- 接着,主线程开始执行任务队列中的任务。由于定时器时间到达,setTimeout 的回调函数被放入任务队列中。
- 最后,主线程执行任务队列中的任务,打印 "setTimeout"。
综上所述,JavaScript 的执行机制遵循以下步骤:
- 执行同步代码,将函数调用和变量分配到调用栈中按顺序执行。
- 遇到异步操作,如定时器、事件监听等,将其注册到任务队列中,并继续执行后续的同步代码。
- 同步代码执行完毕后,主线程会检查微任务队列,依次执行队列中的微任务(Promise 回调函数)。
- 执行完微任务后,主线程会从任务队列中取出任务执行,执行完一个任务后再检查微任务队列,如此循环,直至任务队列为空。
需要注意的是,微任务优先级高于任务队列中的任务,所以在执行任务队列中的任务之前,会先执行完所有的微任务。
现学现用,再看一个例子:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("js start");
setTimeout(function () {
console.log("timeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});
console.log("js end");
这段代码的打印顺序如下:
- "js start":立即打印,表示 JavaScript 代码的开始执行。
- "async1 start":由于 async1 函数被调用,所以会打印 "async1 start"。
- "async2":async1 函数中调用了 async2 函数,因此会打印 "async2"。
- "promise":new Promise 的回调函数立即执行,所以会打印 "promise"。
- "js end":立即打印,表示 JavaScript 代码的执行结束。
- "async1 end":由于 async2 函数是一个异步函数,await async2() 表达式会等待 async2 函数执行完毕,然后继续执行下面的代码,所以会打印 "async1 end"。
- "then":Promise 的 then 方法是异步执行的,所以会在下一个事件循环中执行,因此会打印 "then"。
- "timeout":由于 setTimeout 的延迟时间为 0,所以会在下一个事件循环中执行,因此会打印 "timeout"。代码的执行顺序是按照同步代码的顺序执行,异步代码则根据事件循环的机制来执行。async/await 会暂停同步代码的执行,并等待异步操作完成后再继续执行后续的代码。
总结
JS 代码的执行顺序主要为:
- 同步代码 同步代码(sync code)直接进入执行栈执行。执行顺序按代码书写顺序。
- 异步任务回调 异步任务(如 setTimeout)进入任务队列。
- 事件循环 事件循环周期性地从任务队列取出任务,推入执行栈执行。当执行栈为空时,才会取出队列中的任务。
- 执行栈先进后出 执行栈采用先进后出的方式执行函数。在函数执行完毕后才会执行上层函数。这保证了函数的正确嵌套调用。
- 微任务优先级高于宏任务
- 宏任务(macrotask):出于任务队列的任务。比如 setTimeout、setInterval。
- 微任务(microtask):比如 Promise .then、MutationObserver 。微任务的优先级高于宏任务。所以整个执行顺序可以描述为:
- 同步代码按顺序进入执行栈执行
- 异步宏任务进入任务队列
- 当执行栈清空时,执行微任务
- 接着执行宏任务
- 循环往复