网站

JavaScript 运行机制 - W3School

并发模型与事件循环 - JavaScript | MDN

引入

JS 一次只能做一件事。

代码从上往下顺序执行,如果中间有个操作很慢(比如请求网络数据),后面的代码就得一直等着。

为了让慢操作不卡住后面的代码,JS 引入了同步和异步的概念,配合调用栈和任务队列来协调执行。

正文

定义

定义

JS 执行机制是指 JS 代码从进入运行到执行完成的整个流程。核心包括:调用栈(执行同步代码)、任务队列(存放异步回调)、事件循环(协调两者的运转)。

单线程

JS 是单线程语言,同一时刻只能执行一段代码。

这是为了避免多个操作同时修改页面,造成冲突。

比如一个脚本在删除元素,另一个同时在添加元素,页面就乱套了。

单线程意味着代码要排队执行,所以不能让慢操作堵住后面的代码。

同步和异步

同步代码:按顺序一行一行执行,上一行没完成,下一行不开始。

console.log("第一步");
console.log("第二步");
console.log("第三步");

输出:

第一步
第二步
第三步

异步代码:把慢操作交给浏览器(或 Node.js),自己继续执行后面的代码。等慢操作完成后,再回来处理结果。

console.log("开始");
 
setTimeout(function () {
  console.log("定时器完成");
}, 1000);
 
console.log("结束");

输出:

开始
结束
定时器完成

setTimeout 把定时器任务交给浏览器,JS 自己继续往下走。

等 1 秒后定时器到了,浏览器再通知 JS 回来执行回调。

调用栈

调用栈是一个后进先出的结构,用来执行同步代码。

代码执行时,函数被压入栈顶,执行完后弹出。

function a() {
  console.log("a");
}
 
function b() {
  a();
  console.log("b");
}
 
b();

调用栈的变化过程:

1. b() 进入调用栈
2. a() 进入调用栈(在 b 上面)
3. a() 执行完毕,弹出调用栈
4. b() 继续执行,弹出调用栈

输出:

a
b

栈底是先进去的,栈顶是当前正在执行的。

任务队列

异步操作完成后,回调函数不会马上执行,而是先放到任务队列里排队。

任务队列是先进先出的结构,谁先到谁先执行。

setTimeout(function () {
  console.log("第一个");
}, 1000);
 
setTimeout(function () {
  console.log("第二个");
}, 500);

输出:

第二个
第一个

500ms 的先到队列,所以先执行。

事件循环

事件循环(Event Loop)是 JS 执行机制的核心调度员。

它不停地在做一件事:

  1. 检查调用栈是不是空的
  2. 如果空了,就去任务队列里拿一个回调放到调用栈里执行
  3. 执行完再检查调用栈,重复这个过程

用一个图来表示整个流程:

同步代码
    │
    ▼
调用栈(执行)
    │
    │  遇到异步操作
    ▼
交给浏览器处理
    │
    │  异步完成
    ▼
回调进入任务队列
    │
    │  调用栈空了
    ▼
事件循环把回调推到调用栈
    │
    ▼
继续执行

完整的执行流程

看一段综合代码:

console.log("1");
 
setTimeout(function () {
  console.log("2");
}, 0);
 
console.log("3");

执行过程:

  1. console.log("1") 进入调用栈,执行,输出 1,弹出
  2. setTimeout 进入调用栈,把定时器交给浏览器,弹出
  3. console.log("3") 进入调用栈,执行,输出 3,弹出
  4. 调用栈空了,定时器已到时间,回调进入任务队列
  5. 事件循环把回调推到调用栈,执行,输出 2,弹出

输出:

1
3
2

即使 setTimeout 设置 0 毫秒,回调也要等同步代码全部执行完才轮到它。

同步异步对比

对比项同步异步
执行方式按顺序逐行执行交给浏览器,完成后回调
是否阻塞会阻塞后面的代码不阻塞
常见场景普通计算、DOM 操作定时器、网络请求、文件读取
执行位置调用栈先浏览器处理,再任务队列

特点

  • JS 是单线程,同一时刻只做一件事
  • 同步代码在调用栈中直接执行
  • 异步操作的回调放入任务队列等待
  • 事件循环负责在调用栈空时把队列里的任务拿出来执行
  • 同步代码一定比异步回调先执行

理解

把整个执行机制理解成一家餐厅

调用栈是厨房的灶台,一次只能炒一个菜(单线程)。

同步代码就是普通的菜,来了直接上灶台炒。

异步操作像是炖汤,交给旁边的慢炖锅(浏览器),不用占灶台。

汤炖好了,服务员把”汤好了”的纸条放到队列里(任务队列)。

事件循环就是厨房调度员:灶台空了,就看队列里有没有等着的纸条,有的话就把那道菜安排上灶台。

引出

理解了执行机制的基本流程后,可以继续学习 JS 事件循环,深入了解宏任务和微任务的区别。

也可以学习 JS 异步编程,了解异步代码的不同写法和演变过程。