关于javascript:看完还不懂JavaScript执行机制EventLoop你来捶我

33次阅读

共计 4129 个字符,预计需要花费 11 分钟才能阅读完成。

上一篇文章介绍了过程与线程,晓得渲染过程都有一个主线程,并且主线程工作很多,要解决 DOM、计算款式、布局、还有鼠标、键盘等各种 JS 工作

咱们都晓得 JS 是单线程,工作只能一件一件地执行,那么浏览器是怎么让这么多类型的工作在主线程上有条紊地执行的呢?

这就须要 工作队列 事件循环

工作队列(音讯队列)

什么是工作队列呢?

它是一种数据结构,寄存要执行的工作。而后事件循环系统再以先进先出准则按程序执行队列中的工作。产生新工作时 IO 线程就将工作增加在队列尾部,要执行工作渲染主线程就会循环地从队列头部取出执行,如图

如果其余过程也有工作想让主线程执行的话,也是一样的通过 IO 线程接管并将工作增加到工作队列就能够了

可工作队列里的工作类型太多了,而且是多个线程操作同一个工作队列,比方鼠标滚动、点击、挪动、输出、计时器、WebSocket、文件读写、解析 DOM、计算款式、计算布局、JS 执行 …..

这些工作都在主线程中执行,而 JS 是单线程的,一个工作执行须要等后面的工作都执行完,所以就须要解决单个工作占用主线程过久的问题

比方如果一个动画工作后面有一个 JS 工作执行工夫很长,那咱们看到的就是卡卡的感觉,用户体验就很不好

如果是 DOM 频繁发生变化的 JS 工作,每次变动都须要调用相应的 JavaScript 接口,无疑会导致工作工夫拉长,如果把 DOM 变动做成异步工作,那可能增加到工作队列过程中,后面又有很多工作在排队了

所以 为了解决高优先级的工作 和解决单任务执行过长的问题,所以须要将工作划分,所以微工作和宏工作它来了

在说微工作之前,要晓得一个概念就是 同步 异步

同步和异步

咱们晓得了浏览器页面是由工作队列和事件循环系统来驱动的,然而队列要一个一个执行,如果某个工作 (http 申请) 是个耗时工作,那浏览器总不能始终卡着,所以为了避免主线程阻塞,JavaScript 又分为同步工作和异步工作

同步工作:就是工作一个一个执行,如果某个工作执行工夫过长,前面的就只能始终等上来

异步工作:就是过程在执行某个工作时,该工作须要等一段时间能力返回,这时候就把这个工作放到专门解决异步工作的模块,而后持续往下执行,不会因为这个工作而阻塞

也就是说,除了工作队列,还有一个专门解决须要提早执行的模块(提早哈希表)

常见的异步工作:定时器、ajax、事件绑定、回调函数、async await、promise

好了,咱们再来说微工作吧

微工作和宏工作

JS 执行时,V8 会创立一个全局执行上下文,在创立上下文的同时,V8 也会在外部创立一个微工作队列

有微工作队列,天然就有宏工作队列,工作队列中的每一个工作则都称为宏工作,在以后宏工作执行过程中,如果有新的微工作产生,就增加到微工作队列中

  • 微工作包含:promise 回调、proxy、MutationObserver(监听 DOM)、node 中的 process.nextTick 等
  • 宏工作包含:渲染事件、申请、script、setTimeout、setInterval、Node 中的 setImmediate、I/O 等

来看栗子搞懂她

你和一个大爷在银行办业务,大爷排在你后面,大爷是要存钱,存完钱之后,工作人员问大爷还要不要办理其余业务,大爷说那我再改个明码吧,这时候总不能让大爷到队伍最初去排队再来改明码吧

这外面大爷要办业务就是一个宏工作,而在钱存完了又想改明码,这就产生了一个微工作,大爷还想办其余业务就又产生新微工作,直到所有微工作执行完,队伍的下一个人再来

这个队伍就是工作队列,工作人员就是单线程的 JS 引擎,排队的人只能一个一个来让他给你办事

也就是说 以后宏工作里的微工作全副执行完,才会执行下一个宏工作

用代码来举例

<script> // 宏工作
    console.log(1)
    setTimeout(()=>{ // 宏工作
        console.log(2)
    },0)
    console.log(3)
</script>

输入后果就是 1 3 2,因为 setTimeout 是宏工作,哪怕它的工夫为 0,以后宏工作里的工作没执行完,她插队也没用。而后就算计时工夫为 0,它也是一个提早工作,所以放到异步解决模块去先

留神 :异步解决模块(提早哈希表) 是一个和工作队列同等级的数据结构。每个宏工作完结后,主线程就会查看提早哈希表,将外面到期的工作拿进去顺次执行,比方回调 / 计时器达到触发条件等。不明确的话上面有图,看着就很清晰了

再来

<script> // 宏工作
    console.log(1)
    new Promise( resolve => {resolve(2) // 回调 是微工作
        console.log(3)
    }).then( num => {console.log(num)
    })
    console.log(4)
</script>

输入后果就是 1 3 4 2,遇到微工作 (这里的回调) 就放到微工作队列里,等着执行栈中的工作执行完,再拿进去执行

看图,必须要搞懂她

没了解的话能够多看一会儿这个图,这一块儿也是面试很爱问的

如图能够看进去执行过程造成了一个循环,这就是 事件循环(EventLoop)

事件循环(EventLoop )

事件循环:一句话概括就是入栈到出栈的循环

即:一个宏工作,所有微工作,渲染,一个宏工作,所有微工作,渲染 …..

循环过程

  1. 所有同步工作都在主线程上顺次执行,造成一个执行栈(调用栈),异步工作解决完后则放入一个工作队列
  2. 当执行栈中工作执行完,再去查看微工作队列里的微工作是否为空,有就执行,如果执行微工作过程中又遇到微工作,就增加到微工作队列开端继续执行,把微工作全副执行完
  3. 微工作执行完后,再到工作队列查看宏工作是否为空,有就取出最先进入队列的宏工作压入执行栈中执行其同步代码
  4. 而后回到第 2 步执行该宏工作中的微工作,如此重复,直到宏工作也执行完,如此循环

练习一下,彻底搞懂她

<script>
    setTimeout(function () {console.log('setTimeout')
    }, 0)
    new Promise(function (resolve) {console.log('promise1')
        for(let i = 0; i < 1000; i++) {i === 999 && resolve()
        }
        console.log('promise2')
    }).then(function ()  {console.log('promise3')
    })
    console.log('script')
</script>

输入后果:promise1 -> promise2 -> script -> promise3 -> setTimeout

想一下为什么?

  • script 是宏工作,先执行它外面的微工作
  • 遇到宏工作 setTimeout 放到异步解决模块(提早哈希表)
  • 继续执行 promise,打印promise1
  • 遇到循环,执行,遇到回调 resolve(),下面说了回调属于微工作,放到微工作队列
  • 继续执行,打印 promise2
  • 继续执行,打印 script
  • 执行栈的工作执行完了,去微工作列队里拿
  • 有一个 then 回调,执行,打印 promise3
  • 微工作都执行完了,去工作队列拿下一个宏工作
  • 执行 setTimeout,打印 setTimeout

没有了解的话,再想想

当遇到 async / await 呢?

async/await 是 ES7 引入的重大改良的中央,能够在不阻塞主线程的状况下,应用同步代码实现异步拜访资源的能力,让咱们的代码逻辑更清晰

说白了

async:就是异步执行和隐式返回 Promise
await:返回的就是一个 Promise 对象

看题

async function fun() {console.log(1)
    let a = await 2
    console.log(a)
    console.log(3)
}
console.log(4)
fun()
console.log(5)

输入后果:4 1 5 2 3

联合 async / await 的特点,咱们来把这个题用 ES6 翻译一下

function fun(){return new Promise(() => {console.log(1)
        Promise.resolve(2).then( a => {console.log(a)
            console.log(3)
        })
    })
}
console.log(4)
fun()
console.log(5)

下面说了,回调是微工作,所以间接扔到微工作队列等着,这题里天然就是最初执行,是不是好了解一点了

再来

先别看上面答案,想一下这题,和下面有一点点区别

function bar () {console.log(2)
}
async function fun() {console.log(1)
    await bar()
    console.log(3)
}
console.log(4)
fun()
console.log(5)

输入后果:4 1 2 5 3

为啥?下面例子中 2 都没打印进去,为啥这个就进去了

因为 await 的意思就是等,等 await 前面的执行完。所以 ”await bar()”,是从右向左执行,执行完 bar(),而后遇到 await,返回一个微工作(哪怕这工作里没货色),放到微工作队列让出主线程。

下面说了 async/await 就是把异步以同步的模式实现,同步就是一步一步一行一行来嘛,await 在微工作队列里都没回来,那在 await 上面的天然不能执行,导致 3 最初打印

上面还有一题,把下面几题联合了,我就不写答案了

你来搞定她

async function async1 ()  {console.log('async1 start');
    await  async2();
    console.log('async1 end')
}

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')

结语

点赞反对、手留余香、与有荣焉

正文完
 0