event loop 事件循环学习

842 阅读5分钟

1.js的由来

JS 创始人 Brendan Eich
  1. 主要是解决当时表单提交的时候一些校验处理,因为当年带宽比较小,如果没有校验每次重复提交很浪费资源。
  2. js是为浏览器服务的,浏览器统一调度js

js文档: 不包括event loop
html文档:反而包含event loop

事件循环描述: 浏览器用于协调用户交互包括:鼠标,键盘,JS,渲染,网络等行为的机制。 js没有权限去获取系统的时间,都是被动的,依赖与浏览器 大白话:js没有事件循环,是浏览器的通过事件循环-控制了不同的事件源的交互。

2.异步

大部分事件源都是异步的,而且是队列形式-排队执行。

2.1. 两种队列

  1. 宏队列:宏的都是事件,宏每次只执行一次,串行执行 浏览器的各个事件的队列 用户交互(鼠标,键盘) dom操作(页面渲染) 定时器(settimeout) 网络请求(ajax) historyAPI
  2. 微队列 js微执行的任务队列 promise mutaionObsve object.observe已废弃

2.2.执行流程

1.一次宏任务
2.清空所有微任务
3.Html渲染
4.回到1 

image.png

注意:每一个<script></script>都是一个事件源宏队列,如果有多个 按顺序执行宏队列

<html>  
    <script >
        console.log("aaa") 
        Promise.resolve().then(() => {
            console.log("111")
        })
    </script>

    <script >
    console.log("bbb") 
    Promise.resolve().then(() => {
        console.log("222")
    })
</script> 
</html>

2.3.从js的执行栈分析宏队列与微队列关系

image.png

  1. 每次左边执行栈都会加入由宏任务和微任务的方法到 执行栈里。
  2. 添加方式:宏现任先添加最前面一个,然后再添加所有微任务。
  3. 执行流程:先执行放入到执行栈里的宏任务,再执行所有微任务

注意:在代码的根地方console.log()输出也是一种宏任务

栗子

console.log("1")

setTimeout(() => {
    console.log("2")
}, 0);

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

console.log("5")
//1
//5
//3
//4
//2

3.在node.js的事件循环

1.原理

node.js 中 js 依附于libuv ,取消所有宏输入(如键盘,鼠标),只保留了宏队列和微队列,新增I/O

image.png

2.与浏览器区别

  1. 事件循环的过程没有 HTML 渲染。只剩下了宏队列和微队列这两个部分。
  2. 宏队列的事件源不同。Node.js 端没有了⿏标等外设但是新增了⽂件等 IO。

3.不同版本-执行差异

node.js 10版本 11版本 或者之前的版本 eventloop事件是执行完所有相同类型宏事件,再执行微事件

node.js 12版本 eventloop事件是跟浏览器一致,一次只执行一个宏事件,同时清空宏下的所有微事件

4.栗子

setTimeout(() => {
    console.log("settimeout 1")
    Promise.resolve().then(() => {
        console.log("promise 1")
    })
}, 0);

setTimeout(() => {
    console.log("settimeout 2")
    Promise.resolve().then(() => {
        console.log("promise 2")
    })
}, 0);

setImmediate(() => {
    console.log("setImmediate 3")
    Promise.resolve().then(() => {
        console.log("promise 3")
    })
}, 0); 
setImmediate(() => {
    console.log("setImmediate 4")
    Promise.resolve().then(() => {
        console.log("promise 4")
    })
}, 0); 

// node.js 12版本
// eventloop事件是跟浏览器一致,一次只执行一个宏事件,同时清空宏下的所有微事件
// setImmediate 优于setTimeout先执行

// setImmediate 3
// promise 3
// setImmediate 4
// promise 4
// settimeout 1
// promise 1
// settimeout 2
// promise 2

// node.js 10版本
// eventloop事件是执行完所有相同类型宏事件,再执行微事件
// setImmediate 3
// setImmediate 4
// settimeout 1
// settimeout 2

// promise 3
// promise 4
// promise 1
// promise 2

4.新方法 setImmediate

setImmediate 是node.js 新增的独有方法

#解决setTimeOut精度问题
//这里设置的0秒是有误差的,因为循环是依赖浏览器执行
setTimeout(()=> {
},0)

setImmediate 优于setTimeout 立即执行

setTimeout(() => {
    console.log('timeout');
}, 0)
setImmediate(() => {
    console.log('immediate')
})
//immediate
//timeout

5.process.nextTick

这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。

setTimeout(() => {
    console.log('timer1')
    Promise.resolve().then(function () {
        console.log('promise1')
    })
    //process.nextTick 也优先于微任务
    process.nextTick(() => {
        console.log('nextTick1')
    })
}, 0)

//process.nextTick 优先于宏任务
process.nextTick(() => {
    console.log('nextTick2')
    process.nextTick(() => {
        console.log('nextTick3')
    })
})
//    nextTick2
//    nextTick3
//    timer1
//    nextTick1
//    promise1

promise async await

Promise是一个构造函数,调用的时候会生成Promise实例。当Promise的状态改变时会调用then函数中定义的回调函数。我们都知道这个回调函数不会立刻执行,他是一个微任务会被添加到当前任务队列中的末尾,在下一轮任务开始执行之前执行。

async/await成对出现,async标记的函数会返回一个Promise对象,可以使用then方法添加回调函数。await后面的语句会同步执行。但 await 下面的语句会被当成微任务添加到当前任务队列的末尾异步执行。

async function async1(){
    console.log('async1 start')
    await async2() //这里等价于同步代码,里面如果有promise 也是优先于下面全局的 promise先调用。
    console.log('async1 end') //这里的代码相当与包装成 then 执行,会当微任务放入微任务队列里。等待上面的所有同步和异步都执行完。
}
async function async2(){
    console.log('async2')
}
console.log('script start')
setTimeout(function(){
    console.log('setTimeout') 
},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
console.log('script end') 
//node 12输出内容
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

巩固练习



console.log('script start')

async function async1() {
    console.log('async1 111')
    await async2() //注意在await 后面的任务都会到最后 才放入微任务队列,等最后才执行
    await async3() //必须等待上面的微任务结束后,才执行这里的代码
    console.log('async1 222')//这里会放到最后当微任务执行
}

async function async2() { //当前的promise的每一个then会和全局的promise的then 一起按顺序交替调用
    console.log('async2 111')
    return Promise.resolve().then(()=>{
        console.log('async2 222')
    }).then(()=>{
        console.log('async2 333')
    }).then(()=>{
        console.log('async2 444')
    })
}

async function async3() {
    console.log('async3 111')
    return await Promise.resolve().then(()=>{
        console.log('async3 222')
    }).then(()=>{
        console.log('async3 333')
    }).then(()=>{
        console.log('async3 444')
    })
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise 111') //这里是定义 同步代码
    resolve()
})
.then(function() {
    console.log('Promise 222')
})
.then(function() {
    console.log('Promise 333')
})

console.log('script end')

// script start
// async1 111
// async2 111
// Promise 111
// script end
// async2 222
// Promise 222
// async2 333
// Promise 333
// async2 444
// async3 111
// async3 222
// async3 333
// async3 444
// async1 222
// setTimeout