zoukankan      html  css  js  c++  java
  • 粗略讲一讲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的执行机制

     

  • 相关阅读:
    Codeforces Round #630 (Div. 2) E. Height All the Same(组合数学 快速幂 逆元)
    Codeforces Round #627 (Div. 3) F. Maximum White Subtree(树型dp 换根法)
    Codeforces Round #630 (Div. 2) F. Independent Set (树型dp)
    权值线段树 简单总结 相关例题
    Codeforces Round #631 (Div. 2) D. Dreamoon Likes Sequences (bitmasks +dp )
    2018,奔波与意义
    geopandas overlay 函数报错问题解决方案
    使用Python实现子区域数据分类统计
    我要做数据分析
    geotrellis使用(四十二)将 Shp 文件转为 GeoJson
  • 原文地址:https://www.cnblogs.com/h246802/p/9209029.html
Copyright © 2011-2022 走看看