JavaScript 运行机制

一、单线程

JavaScript 是单线程,H5 允许 JavaScript 创建多个线程,但是子线程受主线程控制,并且不能操作 DOM
所有任务分为同步任务和异步任务

  • 同步任务:在主线程中按顺序执行,这些任务形成一个执行栈
  • 异步任务:不会立即执行,放到任务队列中,当异步任务完成后,会将对应的事件放到任务队列中

二、异步执行机制

2.1 执行栈

存放同步任务,按照任务的顺序处理

2.2 任务队列

异步任务的结果放在任务队列中,等待主线程的处理。本身不会立马执行,而是等待执行条件满足时(如 I/O 操作完成、定时器到期)才会执行

2.3 事件循环

当执行栈中同步任务全部完成后,事件循环会检查任务队列中是否有异步任务的结果
如果有待处理的异步任务,事件循环就会将这些任务逐个取出,放到执行栈中执行
主线程会不断重复这个过程:执行栈中的同步任务,检查任务队列中的异步任务

三、宏任务、微任务

3.1 宏任务

宏任务通常是指在事件循环的每一个循环中执行的任务,主要由 JavaScript 引擎和浏览器控制
常见的宏任务:

  • script
  • setTimeout
  • setInterval
  • setImmediate(Node.js 环境)
  • I/O 操作
  • UI 渲染(例如,浏览器的页面渲染和重绘)
  • postMessage(跨线程消息传递)
  • requestAnimationFrame(浏览器的动画帧请求)

3.2 微任务

微任务是在当前宏任务完成后、下一个宏任务开始之前执行的任务,微任务主要用于处理异步操作的回调
常见的微任务:

  • Promise 的 then/catch/finally(非 new Promise)
  • MutationObserver(监听 DOM 变动,浏览器环境)
  • process.nextTick(Node.js 环境)
  • Object.observe

3.3 执行顺序

1)执行当前宏任务: 事件循环从宏任务队列中取出一个任务并执行,例如 setTimeout 的回调

2)执行所有微任务: 当前宏任务完成后,事件循环会处理所有在微任务队列中的任务,微任务队列会在宏任务队列之前被清空

3)渲染和更新 UI: 在微任务队列执行完后,浏览器会更新渲染,确保 DOM 和 UI 的改变被显示出来。此时,浏览器会执行重绘和布局操作

4)执行下一个宏任务: 事件循环继续到下一个宏任务,并重复上述过程

注意: setTimeOut 并不是直接的把回调函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回调函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么 setTimeOut 不能精准执行的问题了。setTimeOut 执行需要满足两个条件:

  • 主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行回调函数
  • 这个回调函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行

四、async、await

async/await 是异步编程的终极解决方案
async 是"异步"的意思,而 await 是等待的意思,await 用于等待一个异步任务执行完成的结果

  • async/await 是一种编写异步代码的新方法(以前是采用回调和 promise)
  • async/await 是建立在 promise 的基础上,像 promise 一样,也是非阻塞的
  • async/await 让异步代码看起来、表现起来更像同步代码

async 修饰的函数就是异步函数,该函数的返回值是 promise 对象。await 只能写在 async 修饰的函数里,await修饰的代码会等待。在函数里,碰到 await 修饰的代码时,await 朝后的代码都会等待

尤其需要注意的是async,await,promise,setTimeout 的执行顺序,具体例子如下:

async function async1() {
        console.log('async1 start');
        await async2();
        console.log('asnyc1 end');
}
async function async2() {
        console.log('async2');
}
console.log('script start');
setTimeout(() => {
        console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
        console.log('promise1');
        reslove();
}).then(function () {
        console.log('promise2');
})
console.log('script end');

执行顺序:
script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut

执行的流程:

- 整个代码片段(script)作为一个宏任务执行console.log('script start'),输出script start
- 执行setTimeout,是一个异步动作,放入宏任务异步队列中
- 执行async1(),输出async1 start,继续向下执行
- 执行async2(),输出async2,并返回了一个promise对象
  await让出了线程,把返回的promise加入了微任务异步队列
  所以async1()下面的代码也要等待上面完成后继续执行
- 执行 new Promise,输出promise1,然后将resolve()放入微任务异步队列
- 执行console.log('script end'),输出script end
到此同步的代码就都执行完成了,然后去微任务异步队列里去获取任务
- 接下来执行resolve(async2返回的promise返回的),输出了async1 end
- 然后执行resolve(new Promise的),输出了promise2
- 最后执行setTimeout,输出了setTimeout

在第4步中, await 这里有一个机制, 就是 await 的等待, 不会阻塞外部函数的执行, 而 await 等待的 如果是一个 Promise 则 Promise 里面的代码还是同步执行;如果不是 Promise ,就会使用 Promise.resolve 来进行封装, 这里的 async2 是一个 async 方法, 里面的打印会同步执行, 而 await async2() 后面的代码会放到微任务队列中的第一个位置,等待外部同步代码执行完毕以后再执行

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值