粗略讲一讲js的代码执行机制

讲一讲js的代码执行机制

从线程和进程讲起

Q:浏览器是进程还是线程,进程和线程的区别是什么?

A:浏览器是多个进程的集合,而线程属于进程。浏览器中的一个窗口就是一个进程

Q:js既然是一个单线程语言,它是怎么完成ajax请求的异步操作的。

A:js本身是无法做到这一点的,它在同一时间只能做一件事情,而ajax请求是由浏览器(准确来说是当前这个窗口进程)新开了一个线程请求,如果这个ajax线程的状态发生变更时,如果已经设置好回调,事件回调的时候是放入Event loop单线程事件队列等候处理。

        Q:异步以及定时器的代码到底是怎么执行的呢?

用我自己的语言解释js的执行机制

  • JavaScript是按照语句的出现顺序进行执行的

 

这个是符合按照语句出现顺序执行的

      上面的例子是最简单的,但工作的时候或者面试的时候,问题不会这么简单,常常是各种定时器以及promise的集合在一起运行

可能面试会这么问,下面这种情况代码打印顺序如何?

 

Q:打印结果是什么?

A:2,4,3,1

Q:为什么会这样?

A:由于js的事件循环处理逻辑造成的(本篇文章就是在解释这东西)

因为js是单线程的,就相当于一个处理业务很快的窗口,我们的代码在排队,一个一个来,没办法,这时候假如有人需要填写一资料之类的不需要柜员协助的事情,如果这个人长时间待在窗口,那排队的效率自然会大大降低。银行的解决办法有一种是这样的,当排到这个顾客需要额外花费一些时间且不需要柜员协助的事情时,顾客会被叫到旁边(js中就是所谓的Event table)去操作,完成以后,也就是有了结果,此事顾客重新叫号,等到窗口排队人群已经做完业务,自然这个顾客(异步的代码)再由柜员处理。

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。

实际是比较复杂的,但简单用文字表示是这样的,

任务进入执行栈——判断是异步还是同步

  1. 同步代码,进入到主线程,异步代码进入Event Table并注册回调函数
  2. 同步代码和异步代码同一时间在执行,此时没有先后关系,当异步代码指定的事情完成以后(例如定时器3000毫秒后),回调函数放入到Event Queue(事件表)
  3. 异步代码等待主线程代码执行完成,完成后按照先进先出的顺序读取Event Queue(事件队列),并依次执行。
  4. 上述过程不断重复,这就是js执行的事件循环Event Loop

盗图如下:

 

那么问题来了,各位同学肯定经历过这种情况吧,轮播图有时候的异常,有时候我们定时3秒翻一页,可是很气的是,有时候我们刚刚进入页面的时候,我们会发现它会在一段时间以内连续翻页2~n次的情况。

要解释这个问题,那我就从setTimeout 讲起。

异步setTimeout

 

我们先来看一个例子,js靠自己的单线程制造定时器

 

这是我们的代码执行js线程时间,我们可以看到,js线程有3049.7ms在工作。原因是因为sleep占了3秒的主线程,可是我们的代码还要执行啊,所以我们看到的是我们的代码执行时间达到了3001,这是很危险的,我们不应该让我们的js线程一个人干这种耗时太久的活。

所以解决办法是这样的,把锅甩给浏览器,让浏览器来把代码监听,3秒后告诉主线程,然后在让主线程工作,这样主线程(也就是js线程)可以休息一下了。

 

我们的js主线程才工作了10.6ms,可喜可贺。

说来这么久,我还是没说轮播图的问题,如果我们把上面两段代码结合起来一起看,我们就可以知道了

打印结果是这样的:

解释一下结果:

  • function(){

         console.log(11,`时间过去了${new Date() - start}`)

   },进入Event Table并注册,计时开始。

  • 执行sleep函数,很慢,非常慢,计时仍在继续。一共需要3秒。
  • 1秒到了,计时事件timeout完成,

    function(){

           console.log(11,`时间过去了${new Date() - start}`)

    }

    进入Event Queue,但是sleep还没执行完,只好等着。

  • sleep终于执行完了,

    function(){

           console.log(11,`时间过去了${new Date() - start}`)

    }

终于从Event Queue进入了主线程执行。

此时打印结果自然是3001ms。

 

以此类推,我们的轮播图现象也是这个原因造成的,

经过n秒以后,定时器已经把多个任务放置到了 Event Queue,但主线程还在忙活。等主线程忙完,事件队列全放了出来,此时我们就可以看到轮播图的快速滚动。

 

特殊的例子,setTimeout(funName,0)

这个试试为什么呢?

只是将当前同步代码执行完成,然后0秒后再执行funName,默认这样写就是说代码在同步代码执行完成后最后执行的意思。

 

引入Promise

 

我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick(这是nodejs的,触及到我的盲区了)

不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。

回到开头的例子

 

代码是这样的

 

  1. 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout。
  2. 遇到Promise,new Promise直接执行,输出2。then被分发到微任务Event Queue中。我们记为then。
  3. 遇到console.log(‘4’), 输出4 ,此时主线程代码执行完成,第一轮宏任务循环结束
  4. 按下图所示,查看有没有微任务Event Queue,此时发现有then这个微任务,执行完所有微任务,输出 3
  5. 执行完微任务,输出2,4,3。以后开始新的宏任务,发现setTimeout 这个宏任务,执行之,输出 1。所以最后结果为 2,4,3,1

 

最后测试一下自己:

 

  1. 整段代码作为第一个宏任务进入js主线程,遇到setTimeout,我们把他放置到宏任务Event Queue中,称呼其为setTimeout
  2. 遇到promise,new Promise() 直接执行,第一次输出 1,以及2,同时将微任务then,叫它then1分发至微任务Event Queue
  3. 继续执行,此时遇到console.log(3) ,直接输出3

到此第一次宏任务执行结束

消息队列中,还存在这这几位大爷

宏任务Event Queue

微任务Event Queue

settimeout

Then1

第一轮的宏任务执行已经结束1,2,3输出,

  1. 进入第二轮执行,按照执行机制,我们在执行完宏任务以后,我们会先找到剩余的所有微任务执行,我们发现微任务then1,执行之,输出5,接着遇到了promise.resolve(7).then(v => console.log(v)), Promise.resolve()直接返回一个promise,回调的then叫它then2,then2注册到微任务Event Queue,同时,then1执行结束后剩下的then,叫它then3注册到微任务Event Queue,这一代码执行完成后
  2. 此时我们的队列是这样的

宏任务Event Queue

微任务Event Queue

settimeout

Then2

 

Then3

6.继续执行微任务 按照队列的先进先出,我们依次执行then2和then3,输出7,6

7.此时所有微任务执行完成,我们继续回到宏任务执行,遇到setTimeout,执行,输出4

Result1,2,3,5,7,6,4

 

最后的总结

  1. Js无论如何都是一个单线程的语言,无论如何异步,它始终还是一个单线程,这是无法改变的,因此它需要寄生于浏览器,不同的浏览器对js的编译都不会一模一样,这也是我们常常头疼的兼容问题
  2. 事件循环(Event Loop)就是js实现异步的一种方法,也就是js的执行机制

 

猜你喜欢

转载自www.cnblogs.com/h246802/p/9209029.html
今日推荐