前端异步事件详解

前端异步事件详解

js 事件机制

javascript 的单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以JavaScript只能是单线程

其他的辅助线程

除了单线程作为主线程之外,还存在着其他线程,例如,处理ajax请求的线程,处理点击事件的线程等等,我们不做区分,暂时管它们叫做辅助线程,也被称为js的 Event Loop

同步与异步

由上可以得出结论,主线程里面的代码就是同步执行的,辅助线程的代码就是异步执行的

主线程同步

主线程运行变量赋值、循环等操作的代码就是同步代码,特点是直接运行,不需要等待,运行的也相对快。

异步代码主要场景

  • click、mouseover等回调函数
  • ajax 请求
  • script 的refer、async 异步加载
  • setTimeout、setInterval
  • promise.then
  • 模块化异步加载场景等

执行机制

浏览器解析js代码时候,如果碰到异步,会将回调函数代码插入Event Loop 任务队列去,当执行引擎空闲时候,从任务队列里面取出来执行,遵循先入先出的原则。

setTimeout(() => {
  console.log(1)
})
setTimeout(() => {
  console.log(2)
})
// log: 1 2

碰到 promise 情况稍稍有些不一样,promise.then 会另外开辟一个任务队列,而且优先执行这个队列,这种异步情况就叫做微任务,队列就叫微任务队列,上面的setTimeout 响应的就叫 宏任务

setTimeout(() => {
  console.log(1)
})
new Promise((resolve) => {
  resolve()
}).then(() => {
  console.log(2)
})
console.log(3)
// log: 3 2 1

理解了这些,看下下面的代码

setTimeout(() => {
  console.log(1)
  new Promise((resolve) => {
    resolve()
  }).then(() => {
    console.log(2)
  })
})

new Promise((resolve) => {
  resolve()
}).then(() => {
  console.log(3)
  setTimeout(() => {
    console.log(4)
  })
})

分析: 宏任务里面有微任务,微任务里面有宏任务,首先第一个打印1的setTimeout 会入宏任务队列, 微任务一结束,打印出,3最先打印出来,然后打印4的setTimeout 会在后面入宏任务队列,然后来执行宏任务1,打印出来1,寻找当前线程的微任务2, 打印出来2,最后执行宏任务4 ,将4打印处理

//log: 3 1 2 4

接下来看以下代码

console.log('1');
setTimeout(function () {
  console.log('2');
  Promise.resolve().then(function () {
    console.log('3');
  })
  new Promise(function (resolve) {
    console.log('4');
    resolve();
  }).then(function () {
    console.log('5')
  })
})
Promise.resolve().then(function () {
  console.log('6');
})
new Promise(function (resolve) {
  console.log('7');
  resolve();
}).then(function () {
  console.log('8')
})
setTimeout(function () {
  console.log('9');
  Promise.resolve().then(function () {
    console.log('10');
  })
  new Promise(function (resolve) {
    console.log('11');
    resolve();
  }).then(function () {
    console.log('12')
  })
})

最终输出:

// 1 7 6 8 2 4 3 5 9 11 10 12

流程如下:(括号内为输出)

  1. 主线程(1)
  2. 主线程(promise 直接执行传入函数)(7)
  3. 主线程的微任务队列(6,8)
  4. 宏任务队列0
  5. 宏任务队列0
  6. 宏任务队列[0]的微任务队列(3,5)
  7. 宏任务队列1
  8. 宏任务队列[1]的微任务队列(11,12)

值得注意的是: 宏任务队列可以有多个,微任务队列在当前线程里只能有一个,可以看成是当前线程结束后的一个回调函数组。

宏任务和微任务

宏任务和微任务的关系

就拿医院挂号排队为例,宏任务就是取号排队的事件,微任务就是可能有个病人他看病过程需要拍x光、去验血,排在后面的一位只能等,这时候拍x光和验血就组成了这个病人(宏任务)的微任务队列。只有当上一个病人x光和验血都进行完之后,才能进行下一个病人诊断。

宏任务 macro-task

宏任务就是指的是浏览器dom对象原生的方法属性和基于此的封装带来的异步执行。

宏任务的来源:

  • xmlHttpRequest
  • addEventListener
  • setTimeout
  • setInterval
  • setImmediate(node.js)
  • I/O(上传文件,解析文件需要)
  • requestAnimationFrame(根据浏览器刷新频率更新操作)
  • 框架的render
微任务 micro-task(Job)

微任务也是先出后进的队列,不同的是它来源于JavaScript,所以导致当前线程只有一个微任务队列,类似于 dom 的 onload 的概念

微任务来源:

  • Promise
  • node 的 process.nextTick
  • Object.observe

无阻塞 I/O Event Loop 机制

图中的event loop中我们假设有A、B、C三个等待执行的命令队列,其中A和B都会在其执行的过程中触发I/O操作(图中右侧红色圆角矩形框,具体I/O操作可举例为“读取数据库数据”)。以A触发自身的I/O操作为例,常规的动态语言可能都会停住整个队列,等待I/O回馈后,才结束中断、继续运行下去。如果遇到I/O很耗时的情况,进程就会白白等待而浪费不少时间。为了解决此问题,NodeJS采用了event loop机制,将所有I/O操作都扔到线程池去处理,从而不再阻塞命令队列的进一步执行操作。因此从上图可以看到,即使A触发了自身的I/O,也不会阻塞队列的下一个命令B的执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值