首页 > 其他分享 >事件循环-同步异步-计时器精确问题

事件循环-同步异步-计时器精确问题

时间:2024-01-26 13:00:30浏览次数:25  
标签:异步 同步 console log 队列 resolve 计时器 Promise setTimeout

消息队列的解释

每个任务都有一个任务类型。
同一个类型的任务必须在一个队列中。
不同类型的任务可以分属于不同的队列中。
在一次事件循环中,浏览器可以【根据实际情况】从不同的队列中取出任务执行。
浏览器必须准备好一个微队列,微队列中的任务优先其他所有类型的任务。

chrome中的常见队列

在 chrome 的实现中,至少包含了下面的队列:
1,延时队列:用于存放计时器到达后的回调任务,优先级 中
2,交互队列:也就我们说的点击事件,浏览器缩放窗口等;优先级高。
3,微队列:用户存放需要最快执行的任务,优先级[最高]

阐述一下 JS的事件循环

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
在Chrome 的源码中,它开启一个不会结束的 for 循环;
每次循环从消息队列中取出第一个任务执行。
而其他线程只需要在合适的时候将任务加入到队列末尾即可。
过去把消息队列简单分为宏队列和微队列,
这种说法目前已无法满足复杂的浏览器环境,
取而代之的是一种更加灵活多变的处理方式。
根据 W3C官方的解释:每个任务有不同的类型,同类型的任务必须在同一个队列。
不同的任务可以属于不同的队列。
不同任务队列有不同的优先级。
在一次事件循环中,由浏览器自行决定取哪一个队列的任务。
但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。

代码执行顺序问题

你现在记住:主线程肯定是最先执行的;
代码必须要等到主线程执行完之后;
才能去消息队列中去拿取任务执行;
然后消息队列中有优先级; 微队列 ==> 交互队列 ==> 延时队列

1,理解先同步-后异步(延时队列)

setTimeout(()=>{
  console.log(1)
},0)
console.log(2)
输出的结果:2 然后是1
为什么呢?
因为:主线程代码肯定是先执行;哪怕你延时0毫秒。
主线程执行完毕后。
然后从消息队列中拿取延时队列执行;
所以先输出了2;然后是1

2,理解先同步-后异步(延时队列)

 function yanShi(time){
  var start = Date.now();
  while(Date.now() - start < time){

  }
}
setTimeout(()=>{
  console.log(2)
},0)
yanShi(1000)
console.log(1)
先执行 yanShi函数;这个函数会延时1s后;
我们去输出1;这个时候我们的主线程已经执行完了;
然后我们去执行延时队列中的代码;输出2;
所以:等待1s后,先输出2;然后是1 

3,new Promise(callback)与 new Promise().then的区别

很多时候,我们都以为new Promise(callback) 是异步的;
其实这个观点是错误的;
当我们调用new Promise(callback)时,它是一个同步代码,回调函数会立即执行。
new Promise().then这个才是异步的;
他们的区别之一就是说:前者是同步的;后者是异步的;
下面我们来看一段代码

4,new Promise(callback)是同步代码的

console.log('start');
const promise1 = new Promise((resolve, reject) => {
  console.log(1)
})
console.log('end');
有些同学认为是 start - end - 1;
有的同学认为是 start- 1 -end
大家可以去输出一下;这里就不说争取答案了.

5,promise.then是异步

console.log('start');
const promise1 = new Promise((resolve, reject) => {
  console.log(1)
  resolve(2)
})
promise1.then(res => {
  console.log(res)
})
console.log('end');
我们先是执行主线的代码;输出start;
new Promise 是一个同步的代码;输出 1;
promise1.then是一个微队列;
需要等待主线程执行完毕之后在执行;所以先输出end;
等待主线执行完毕之后,最后输出 2

6,promise.then要状态发生改变才会执行,否则不会执行then

console.log('start');
const promise1 = new Promise((resolve, reject) => {
  console.log(1)
})
promise1.then(res => {
  console.log(2)
})
console.log('end');

这个输出结果很多小伙伴会认为是: 
start - 1 - end - 2
但是实际2不会输出的;
为啥2不会输出呢?
因为 Promise的then方法必须要等到 padding 状态发生改变时才会触发then方法;
也就时说:要触发then方法必须要调用 resolve 或者 reject才会触发;其他不会触发

7,先主线程 - 计时器 - 微队列

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("2");
    resolve("3");
    console.log("4");
  }, 0);
  console.log(5);
});

promise.then((res) => {
  console.log(res);
});

console.log(6);
我们都知道主线程先执行; 
new Promise下的代码是主线程的;刚刚我们说过:
小技巧:我们可以把 console.log(1) 当作 fn1函数输出1;
所以先输出 1 - 5 - 6
然后我们看 setTimeout[延时队列] 和  promise.then[微队列] 谁先执行;
正常情况下是微任务 >  延时队列
但是 then方法是需要状态改变时才会被触发,要想触发then需要先执行 setTimeout
所以又输出了 2 - 4 -3
//  1 - 5 - 6 - 2 - 4 -3

8,延时队列

const timer1 = setTimeout(() => {
 console.log('1');
  const promise1 = Promise.resolve().then(() => {
    console.log('2')
  })
}, 0)

const timer2 = setTimeout(() => {
  console.log('3')
}, 0)

// 1 - 2 - 3

9,主线程 - 添加到微队列 - 延时队列

setTimeout(()=>{
  console.log(1)
},0)
Promise.resolve().then(function(){
  console.log(2)
})
console.log(3)
同样,我们先去执行主线程的代码;
所以先输出的是3;
然后我们去消息队列中去获取任务;
先拿取微队列,然后是交互队列(这里没有),然后是延时队列;
这里 Promise.resolve().then(Fn)就是把一个函数Fn添加到微队列中;
因此执行2;最后输出1;
3--2--1

把一个任务添加到微队列中的方式

Promise.resolve().then(fn函数)
fn这个函数就会添加到微队列中

10,主线程- 添加到微队列 - 延时队列

function a(){
  console.log(1)
  Promise.resolve().then(function(){
    console.log(2)
  })
}
setTimeout(()=>{
  console.log(3)
  Promise.resolve().then(a)
},0)
Promise.resolve().then(()=>{
  console.log(4)
})
console.log(5)
首先我们执行主线的代码;输出5;
然后我们看是否有微队列;如果有去执行;所以输出4;
微队列执行完后我们看交互队列;没有
我们去执行延时队列;所以输出3;
最后我们执行1;然后数2
最终的结果是:5-4-3-1-2
注意点:延时队列中,有输出语句,有添加到微队列;
它是一个一个去执行;并不会说先去做微队列。
而是按照正常代码执行顺序去做。

11,主线程-立即添加到微队列-延时队列

function a(){
  console.log(1)
  Promise.resolve().then(function(){
    console.log(2)
  })
}

setTimeout(()=>{
  console.log(3)
},0)

Promise.resolve().then(a)
console.log(5)

最后的输出: 5-1-2-3

下面这输出比较难-特别是第2个

console.log('0')

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('ok')
}))

console.log('2')

fn().then(res => {
  console.log(res)
})

console.log('3')
//  微任务 - 延时队列
const promise1 = Promise.resolve().then(() => {
  console.log('1');
  const timer2 = setTimeout(() => {
    console.log('2')
  }, 0)
});
// 延时队列 - 微任务
const timer1 = setTimeout(() => {
  console.log('3')
  const promise2 = Promise.resolve().then(() => {
    console.log('4')
  })
}, 0)
这两个是比较难的;如果小伙伴能正确答对;
就算理解了。
第1个的输出是:0 - 2 - 1 - 3 - ok
第2个的输出是:1 - 3 - 4 - 2

JS中的计时器能够做到精确计时吗?为什么?

不行。因为:
1,计算机硬件没有原子钟,无法做到精确计时。
2,操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,这又带来了偏差。
3,按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,至少有4毫秒的偏差。
4,受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差
因此不能够准确计时。 
也就是说:setTimeout,setInterval,的计时是不准确的。

计时器嵌套层级超过 5 层,至少有4毫秒的偏差的解释

setTimeout(function(){
  setTimeout(function(){
    setTimeout(function(){
      setTimeout(function(){
        setTimeout(function(){
          // 这个延时器在第6层,超过了5层,即使我们延时的是0毫秒;
          // 最终也会被修改为4毫秒后执行
          setTimeout(function(){

          },0)
        },0)
      },0)
    },0)
  },0)
},0)

单线程是异步产生的原因。

事件循环是异步的实现方式

标签:异步,同步,console,log,队列,resolve,计时器,Promise,setTimeout
From: https://www.cnblogs.com/IwishIcould/p/17989091

相关文章

  • 如何实现高精度无线同步控制矿山爆破?
        随着现代社会工业化进程的不断加快,人们对于矿山开采的规模和速度要求越来越高。为了快速推进矿山的开采作业,人们对于炸yao威力的要求越来越高。    考虑到生产、运输和存储等各个方面的安全性以及国家有关方面的强制要求,雷管的火药填装量是有严格限制的,不允许......
  • .net 高并发(一,异步编程模型)
    在.NET中,异步编程模型(Async/Await)是一种处理高并发的好方法。它允许开发人员以非阻塞的方式编写异步代码,从而使应用程序能够同时处理多个请求或任务,从而提高并发性能。下面是使用Async/Await进行异步编程的一般步骤:定义一个返回Task或Task<TResult>的方法,并在方法签名中使用as......
  • OpenMP学习 第十一章 同步与OpenMP内存模型
    第十一章同步与OpenMP内存模型内存一致性模型OpenMP线程在共享内存中执行,共享内存是组中所有线程都可以访问的地址空间,其中存储着变量.使共享内存系统高效运行的唯一方法是允许线程保持一个临时的内存视图,该视图驻留在处理器和内存RAM之间的内存结构中.当线程通过共享内存......
  • Vue中JSON文件神奇应用fetch、axios异步加载与模块导入全指南
     在Vue中使用JSON文件有多种方式,包括使用fetch方法加载JSON文件、使用axios库加载JSON文件,以及将JSON文件导入为模块。以下是详细描述和相应的示例代码:1.使用fetch方法加载JSON文件:步骤:创建一个JSON文件,例如 data.json://data.json{"name":"John","age":......
  • jeecg-boot 同步数据库失败,Unable to perform unmarshalling at line number 5 and co
    同步数据库失败,Unabletoperformunmarshallingatlinenumber5andcolumn6.Message:cvc-complex-type.2.4.a:Invalidcontentwasfoundstartingwithelement'{"http://www.hibernate.org/xsd/orm/hbm":property}'.Oneof'{"http://www......
  • MYSQL数据库同步脚本 --仅供参考
      备份同步数据 #!/bin/bash#定义变量user="root"pass="un1ware"host=""file=$(date+"%Y-%m-%d")#使用日期作为文件夹名称#获取主从状态信息master_status=$(mysql--user="$user"--password="$pass"-h"$host"......
  • Supplier 惰性调用和 Future#get 同步等待调用结合
    ......
  • 浙江机电职业技术学院第八届新生亮相赛(同步赛)(小白)
    B.山里灵活/*原石n*10个k颗->一次->90保底*7次到满命->630即可推出公式为n*10/630>=k*/#include<bits/stdc++.h>#defineIOios::sync_with_stdio(false);cin.tie(0);cout.tie(0);usingnamespacestd;typedeflonglongll;voidsolve(){......
  • 大厂咋做多系统数据同步方案的?
    1背景业务线与系统越来越多,系统或业务间数据同步需求也越频繁。当前互联网业务系统大多MySQL数据存储与处理方案:随信息时代爆炸,大数据量场景下慢慢凸显短板,如:需对大量数据全文检索,对大量数据组合查询,分库分表后的数据聚合查询自然想到如何使用其他更适合处理该类问题的数据组......
  • 绿联DH2600将米家设备同步到苹果家庭app(HA)
    方案是通过homekit桥生成一个二维码,然后使用苹果家庭app扫码添加设备进入HA下载homekit桥选择homekitbridge点击提交继续提交点击完成新增的修改集成条目名称(防止你分不清)重命名3.添加设备点击选项如图选择属于客厅的设备4.手机扫码第三步添......