# 事件循环

# event-loop是什么?

js是单线程,异步是基于回调实现的,Event-loop是异步回调的实现原理。

# 事件循环的过程

1599408879599.jpg

console.log('111')
setTimeOut(function){ console.log('333') },1000)
console.log('222')
  • 同步代码一行行执行放在Call Stack中

  • 如果遇到异步,会先记录。时机到了把回调函数放在 callback queue中。 (遇到setTimeOut,是浏览器的API,在webAPI中设置定时器,到达定时器时间把回调函数放入callback queue中)

  • 如果 Call Stack为空(同步代码执行完)Event-loop开始工作

  • 轮询查找 callback queue,如果有则移动到 call statck中执行

  • 然后继续轮询查找

# DOM事件

DOM事件,js执行到DOM事件如click,事件是WebAPI,当用户点击时,把回调函数挂到callback Queue中(但DOM事件不是异步,只是通过Event-loop来实现。)

QQ截图20200203155301.png

# 为什么JavaScript是单线程?

作为浏览器的脚本语言,js主要用途是和用户交互和操作DOM。(如果js一个线程操作DOM,一个线程删除DOM。会带来同步的问题)

为了利用多核计算能力,HTML5提出Web Woker标准允许js创建多线程,但是子进程完全受控于主线程,且不得操作DOM,(并没有改变js单线程的本质)

# 任务队列

主线程:主线程上执行所有的同步任务,主线成的这些同步任务形成一个执行栈。 任务队列:任务队列是一个事件的队列,

(所有的事件都是通过回调实现,都是需要放在异步队列中执行(需要指定回调函数,等待主线程清空队列读取异步队列内容),setTimeOut也是在异步队列中执行,可以通过setTimeOut来模拟异步。)

# setTimeOut(fn,0)的含义

setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。 HTML5 setTimeout第二个参数最小值不能低于4ms,对于DOM的变动(涉及页面重新渲染的那部分),通常不会立即执行,而是每16ms执行一次。 所以此时使用requestAnimationFrame()的效果要好于setTimeOut(). setTimeOut()只是把事件插入到任务队列中,必须等到当前代码主线程执行栈执行完,和异步队列现有任务都执行完,才会执行setTimeOut。

# Node.js的事件循环

# process.nextTick方法

在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。指定的任务总是发生在所有异步任务之前。

     process.nextTick(function A() {
      console.log(1);
      process.nextTick(function B(){console.log(2);});
    });
    
    setTimeout(function timeout() {
      console.log('TIMEOUT FIRED');
    }, 0)
    // 1
    // 2
    // TIMEOUT FIRED`

# setImmediate()方法

在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。

# 宏任务和微任务

  • 宏任务: setTimeOut setInterval Ajax DOM事件
  • 微任务: Promise Async/await
  • 宏任务在DOM渲染后触发,如setTimeOut
  • 微任务在DOM渲染前面触发
//现象 利用alert可以阻塞DOM渲染,观察现象
先打印11,此时DOM还没有渲染。
在打印222,此时页面上已经显示三个插入的p标签
const p1 = $('<p>111</p>')
const p2 = $('<p>222</p>')
const p3 = $('<p>333</p>')
('#container').append('p1').append('p2').append('p3')

Promise.resolve().then(()=>{
  $('#container').children().length //打印3
  alert('11')//打印111 
})

setTimeOut(()=>{
  console.log('222')
})

# 在事件机制中宏任务,微任务执行的过程

  • 1 callStack清空 同步代码执行完
  • 2 执行微任务队列 micro task queue
  • 3 尝试DOM渲染
  • 4 触发Event-loop
  • 5 callbackQueue异步队列事件执行, 宏任务

# setTimeout和setInterval

  • 均为宏任务,计时结束后,在js主线程排队等待执行。
  • 主线程上发发生堵塞,异步队列上的任务不会执行。
  • 因为队列机制,无论是setTimeout还是setInterval,第一次触发时的时间,只会等于大于预设时间
  • setInterval在前一个定时器执行完前,不会向队列插入新的定时器

- 在该代码片段中,首先 100ms后handlerTimeout被添加到任务队列中,之后handlerInterval被添加到任务队列中
- 该代码片段中(其余代码执行180ms)属于同步代码,所以优先执行
- 同步代码180ms执行之后才会执行,handlerTimeout的回调函数。
- handlerInterval 在 handlerTimeout的后面排队,需要等handlerTimeout执行完之后才能执行。

<body>
    <button id="btn"></button>
    <script>
        const btn = document.getElementById("btn");
        btn.addEventListener('click',function handleClick(){
            //...代码执行时间需80ms
        })
    	setTimeout(function handlerTimeout(){
            //...代码执行时间需60ms
        }, 100);
        setInterval(function handlerInterval(){
            //...代码执行时间需80ms
        },100)
        //... 其余代码执行时间需要180ms
    </script>
</body>

# 屏幕帧刷新频率

在苹果机上的最短间隔时间是10毫秒,在Windows系统上的最短间隔时间大约是15毫秒。

大多数电脑显示器的刷新频率是60HZ,大概相当于每秒钟重绘60次。因此,最平滑的动画效的最佳循环间隔是1000ms/60,约等于16.6ms。

综上所述,我认为setInterval的最短间隔时间应该为16.6ms。