JS基础-三座大山之三一异步(1)

今天,来讲一讲异步,讲到异步脑海里不能之单单想到Promise,async,await的,拔出萝卜带出泥,异步涉及到:js的执行机制,事件循环(event loop),宏任务,微任务等等,这篇主要把原理讲明白,要掌握异步还要进行练习,Promise和练习题的讲解将放在JS基础-三座大山之三一异步(2)

带着以下几个问题开始学习吧

  • 进程,线程
  • event loop
  • event loop的机制,可画图
  • 宏任务,微任务,两者的区别

1. 线程和进程的区别

进程

一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

线程:

进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

2. 异步

同步:就是按顺序一行一行执行,如果遇到某一步执行时间非常长也要等他执行完后才往后执行(有点傻)

异步:遇到一个任务,这个任务不知道什么时候能执行完,但是总不能干等着吧,那就这个任务先执行着,我也不等了,我先往后执行,等你任务执行完后返回的结果找个信使传给我就行了

总的来说就是:

  • js 是单线程的,就也就是说同一个时间只能做一件事
  • 作为浏览器脚本语言,与他的用途有关
  • js的主要用途是和用户互动,以及操作DOM,这决定了它只能是单线程

有些任务是耗时的,会阻塞代码执行

如:遇到等待(网络请求,定时任务)不能卡主,所以需要异步,异步不会阻塞代码,同步会阻塞代码

上代码:

console.log('1')
setTimeout(function(){
    console.log('2')
},5000)  // 总不能真的等到5ms后才执行吧?
console.log('3')
// ********************
console.log('1')
console.log('3')
btn.addEventListener('click',function(){  // 总不能等到真的等用户点击后才往后执行吧,玩意万一用户一直不点击我后面的代码就永远不执行吗?
    console.log('2')
})
console.log('3')

所以,为了解决同步会阻塞代码的问题,引入了异步,谈异步就离不开谈事件循环event loop

  • JS是单线程运行的,异步要基于回调来实现,事件循环event loop就是异步回调的实现原理
  • JavaScript 是单线程语言,事件循环是其实现非阻塞异步的核心机制

3. 事件循环/事件轮询 event loop

了解事件循环的好处:

  1. 可以更好的理解代码底层运行机制,夯实基本功

  2. 对学习 promise,vue里的的 $nextTick 等等有很多帮助

3.1 同步代码异步代码

我们可以把代码分为同步代码异步代码

console.log('1') // 同步代码
setTimeout(function(){ // 异步代码
    console.log('2')
},5000)  
console.log('3') // 同步代码

异步代码还有

setTimeout()
setInterval() 定时器
Ajax/Fetch
事件绑定

3.2 js执行机制:

同步代码:立即放入JS引擎(js主线程)执行,并在原地等待结果
异步代码:先放入宿主环境(浏览器/Node),不必原地等待结果,并不阻塞主线程继续往下执行,异步执行结果在将来执行

event loop执行过程:

1 console.log('Hi')

2 setTimeout(function cb1(){
    console.log('cb1')
},5000)

3 console.log('Bye')
  1. 先将 1 放入 调用栈(执行栈)中,执行完后,清空栈,打印 Hi
  2. 执行 2 的时候,setTimeout是一个函数,setTimeout函数是Web API里面执行的,将setTimeout的参数 function cb1放入到 定时器 timer中,等事件到了就把cd1放入到task Queue中。 然后清空调用栈
  3. 将 3 放入 调用栈中,执行完后,清空栈, 打印 Bye
  4. 同步代码执行完毕后,浏览器回立即启动 Event Loop机制,不断轮询查找 task Queue,直到task Queue有内容时,就执行

总结一下这个过程:

  • 同步代码一行一行放在call stack(执行栈/调用栈)执行

  • 遇到异步,会先记录下,等待时机(定时、网络请求等)

  • 时机到了 就将异步移动到task Queue 任务队列中

  • 如call stack 为空(即同步代码执行完成) event loop 开始工作

  • 轮询查找task Queue, 如有则移动到call stack 执行

  • 然后继续轮询查找,像永动机一样

3.3 总结

  • JS是单线程运行的,异步要基于回调来实现,event loop就是异步回调的实现原理

  • JavaScript 是单线程语言,事件循环是其实现非阻塞异步的核心机制

  1. JS是单线程,防止代码阻塞,我们把代码(任务):同步和异步

  2. 同步代码给js引擎执行,异步代码交给宿主环境

  3. 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队

  4. 执行栈执行完毕,回去任务队列看看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程是事件循环(event loop)

上event loop的机制图

事件循环执行过程

4. 事件循环之宏任务和微任务

  • JS把异步任务分为宏任务微任务
    在es5之后,js引入了Promise,这样,不需要浏览器,js引擎自身也能够发起异步任务了

4.1 宏任务 (MacroTask)

  1. 代表离散的异步操作,由宿主环境(浏览器/Node.js)调度,代码中遇到则将它放入 Task Queue队列中

  2. 常见来源:

  • script

  • setTimeout / setInterval

  • I/O 操作(文件读写、网络请求)

  • DOM 事件回调(如 click)

  • setImmediate(Node.js)

4.2 微任务 (MicroTask)

  1. 代表高优先级的异步操作,在同步代码执行后立即执行,微任务是js引擎发起的。码中遇到则将它放入Microtask Queue队列中

  2. 常见来源

  • Promise(Promise本身是同步,then/catch/finally的回调函数是异步)
  • process.nextTick(node)
  • Promise.then() catch()
  • Async/Await
  • Object.observe等等

4.3 执行图

这里介绍一个学习事件循环的网站:JS Visualizer 9000 (jsv9000.app)

事件循环执行图

宏任务微任务执行顺序

分析一下代码:

console.log(100)  // 同步代码 放入执行栈/调用栈中
setTimeout(()=>{
    console.log(200)  // 宏任务 放入Task Queue队列
})

Promise.resolve().then(()=>{  // Promise本身是同步,then/catch/finally的回调函数是异步,是微任务,放入Microtask Queue队列
    console.log(300)
})
console.log(400) // 同步代码 放入执行栈/调用栈中
 
// 100 400 300 200
console.log("Start"); // 1. 同步

setTimeout(() => console.log("Timeout"), 0); // 2. 宏任务

Promise.resolve()
  .then(() => console.log("Promise 1")) // 3. 微任务
  .then(() => console.log("Promise 2")); // 4. 微任务

console.log("End"); // 5. 同步

/**
 * 同步代码	Start → End	执行所有同步代码
 * 清空微任务	Promise 1	执行第一个 then,Promise 2	微任务中触发的微任务会立即执行
 * 执行宏任务	Timeout	取出宏任务队列中的回调执行
 */

// Start → End → Promise 1 → Promise 2 → Timeout

4.4 宏任务,微任务 和 DOM渲染

  • JS是单线程的,DOM渲染和js执行共用一个线程

  • JS执行的时候,得留一些时机供DOM渲染

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件(会直接放入任务队列中)

  • 微任务:Promise async/await

  • 宏任务:DOM渲染后触发,如setTimeout

  • 微任务:DOM渲染前触发。如Promise

  • 微任务执行时机要比宏任务要早?

    因为微任务是在在DOM渲染前触发,宏任务是在DOM渲染后触发。

4.5 DOM事件和 event loop 的关系

因为js是单线程的,DOM事件和异步(setTimeout,ajax等)一样使用了回调的机制,基于event loop,他们的触发时机是由浏览器控制的,但是DOM事件不是异步(DOM事件的执行时机一般取决于用户的操作,比如点击按钮,但是DOM都是回调函数,所以遇到DOM事件,会先记录下,用户一操作就将会调函数让入到Callback Queue中)。

4.5 一个练习

console.log('1')
setTimeout(function(){
    console.log('2')
},4000)
const p = new Promise((resolve,reject)=>{
    console.log('3')
    resolve('1000')  // 标记成功
    console.log('4')
})
p.then(data=>{
    console.log(data)
})
console.log('5')

// 输出结果 1,3,4,5,1000,2

宏任务和微任务代码执行顺序图

5. 总结

  1. JS是单线程运行的,异步要基于回调来实现,事件循环event loop就是异步回调的实现原理
  2. JavaScript 是单线程语言,事件循环event loop是其实现非阻塞异步的核心机制
  3. js代码分为同步代码和异步代码,异步任务又分为宏任务和微任务
  4. js执行代码时,从上到下,遇到同步代码放入到 call stack 执行栈中并且等待结果的输出,如果遇到异步代码,则看看是不是宏任务,是宏任务的话,等待时机成熟之后将宏任务直接放入Task Queue 任务队列中,是微任务的的话,就将微任务放入到 Microtask Queue 微任务队列中,等同步代码执行完毕,js启动事件循环event loop机制开始循环访问微任务队列,有的话,将队列中的任务按照先进先出的规则放入到call stack 执行栈中执行,微任务执行完后,再将Task Queue 任务队列中任务按照先进先出的规则放入到call stack 执行栈中执行,然后反复循环

Promise和练习题的讲解在JS基础-三座大山之三一异步(2)

posted @ 2025-08-18 16:09  cyy618  阅读(8)  评论(0)    收藏  举报