深入了解浏览器中的Event loop,同步异步,宏微任务

我们为什么要会Event loop

•是要增加自己技术的深度,也就是懂得JavaScript的运行机制。
•现在在前端领域各种技术层出不穷,掌握底层原理,可以让自己以不变,应万变。
•应对各大互联网公司的面试,懂其原理,题目任其发挥。

前置知识点

•栈,堆,队列
•同步,异步任务
•宏任务,微任务
•Async/await语法糖

前言

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

js引擎负责解析并编译js代码。制定作用域标准,分配内存,创建执行上下文调用栈…。编译好的代码放到运行环境中去运行,而运行环境会维护异步任务。
对浏览器而言,浏览器是多线程的,它可以分配线程去倒计时定时器,发送请求,事件监听等。当定时器中的事件倒计时完毕,将其扔到也是由浏览器维护的消息队列中,当遇到其他异步时,浏览器会分配进程去处理,处理完毕也是扔到消息队列中。等待js引擎去执行。

(1)堆(Heap)

是一种数据结构,是利用完全二叉树维护的一组数据,分为两种,一种为最大,一种为最小堆,将根节点最大叫做最大堆大根堆,根节点最小叫做最小堆小根堆
线性数据结构,相当于一维数组,有唯一后继。
如最大堆:
深入了解浏览器中的Event loop,同步异步,宏微任务_第1张图片

(2)栈(Stack)

在计算机科学中是限定仅在表尾进行插入删除操作的线性表。 是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底最后的数据栈顶,需要读数据的时候从栈顶开始弹出数据
是只能在某一端插入删除特殊线性表
深入了解浏览器中的Event loop,同步异步,宏微任务_第2张图片

(3)队列(Queue)

特殊之处在于它只允许在表的前端( front )进行删除操作,而在表的后端( rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO-first in first out )
深入了解浏览器中的Event loop,同步异步,宏微任务_第3张图片

同步任务,异步任务

概念解释

同步任务

同步是js任务进入任务栈按顺序等待主线程执行的过程,简单来讲就是js从上到下代码的运行的过程,如果中间有个同步任务出现了死循环,那么浏览器可能就会出现js无响应,意思就是无法继续向下执行。

异步任务

但是我们读取本地文件数据,或者获取服务器接口数据时,花费的时间无法确定,这样会导致页面看起来非常卡顿,严重影响用户体验。这个时候就出现了异步,即不进入主线程任务栈,而是先进入event Table并注册回调函数,然后按顺序进入异步事件队列,在主线程的任务栈执行完后调用(队列先进先出,所以按注册函数进入队列的顺序来调用)

常见的同步和异步任务

同步任务

•console.log()
•new Promise()
•函数调用
•…

异步任务

•Ajax请求
•setTimeOut、setInterval
•promise.then()
•Dom事件
•…

浏览器中的Event Loop

Javascript有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

JS调用栈

JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

同步任务和异步任务执行

Javascript单线程任务被分为同步任务异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到内等待主线程的执行。任务队列Task Queue,即队列,是一种先进先出的一种数据结构。
深入了解浏览器中的Event loop,同步异步,宏微任务_第4张图片

深入了解浏览器中的Event loop,同步异步,宏微任务_第5张图片

宏任务,微任务

概念解释

任务队列中的任务也分为两种,分别是:

宏任务(Macro-take)
微任务(Micro-take)

常见的宏任务微任务

宏任务(MacroTask)

script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。

微任务(MicroTask)

Process.nextTick (Node独有)、Promise 、Async/Await(实际是Promise),object.observe(废弃)、Mutation0bserver

执行流程

事件的执行顺序,是先执行宏任务,然后执行微任务,这个是基础,任务可以有同步任务和异步任务,同步的进入主线程,异步的进入Event Table并注册函数,异步事件完成后,会将回调函数放入Event Queue中(宏任务和微任务是不同的Event Queue),同步任务执行完成后,会从Event Queue中读取事件放入主线程执行,回调函数中可能还会包含不同的任务,因此会循环执行上述操作。
**注意:**setTimeOut并不是直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。setTimeOut执行需要满足两个条件:
1.主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回调函数
2.这个回调函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
深入了解浏览器中的Event loop,同步异步,宏微任务_第6张图片

加入异步Promise的执行

var p=new Promise(resolve=>{
    console.log(4)
    resolve(5)
})


function f1(){
    console.log(1);
}


function f2(){
    setTimeout(()=>{
        console.log(2);
    })
    f1()
    p.then(resolve=>{
        console.log(resolve);
    })
    .then(()=>{
        console.log(6);
    })
    console.log(3);
}


f2()


//4 1 3 5 6 2

Event Loop使用Async/Await

源代码:

async function async1(){
    console.log("async1begin");
    let async2res= await async2()
    console.log(async2res);
    console.log("async1end");
} 
async function async2(){
    console.log("async2begin");
    return "async2end"
} 
async1()
console.log("end");

源代码转promise:

function async1(){
    console.log("async1begin");
    return new Promise((resolve)=>{
        console.log("async2begin");
        resolve("async2end")
    }).then((res)=>{
        console.log(res);
        console.log("async1end");
    })
} 
async1()
console.log("end");

async1begin
async2begin
end
async2end
async1end

加入async/await的执行面试题

async function async1(){
    console.log("A");
    await async2()
    console.log("B");
}    
async function async2(){
    console.log("C");
}
console.log("D");

setTimeout(function(){
    console.log("E");
},0)

async1()

new Promise(function (resolve){
    console.log("F");
    resolve()
}).then(function(){
    console.log("G");
})

console.log("H");

D A C F H B G E

阿里面试题

<body>
  <button >buttonbutton>
body>
<script>
  let button=document.getElementsByTagName('button')[0];
  button.addEventListener("click",()=>{
    Promise.resolve().then(()=>{console.log("M 1");})
    console.log("L 1");
  })
  button.addEventListener("click",()=>{
    Promise.resolve().then(()=>{console.log("M 2");})
    console.log("L 2");
  })
  button.click()
script>

L 1
L 2
M 1
M 2

点击后:

L1
M1
L2
M2

button.click()会把两个函数合起来用,类似:

function fn1(){
  Promise.resolve().then(()=>{console.log("M 1");})
  console.log("L 1");
  Promise.resolve().then(()=>{console.log("M 2");})
  console.log("L 2");
}
fn1()

点击事件会连着执行两个函数,类似:

    function fn2(){
        Promise.resolve().then(()=>{console.log("M 1");})
        console.log("L 1");
    }
    function fn3(){
        Promise.resolve().then(()=>{console.log("M 2");})
        console.log("L 2");
    }
    new Promise((resolve)=>{
        fn2()
        resolve()
    }).then(()=>{
        fn3()
    })

你可能感兴趣的:(周记,js高级,javascript,前端)