消息队列:V8是怎么实现回调函数的?

从内部了解回调函数,可以帮助我们梳理清楚很多问题:
有助于我们理解浏览器中的Web API到底是怎么工作的;
有助于我们理解宏任务和微任务到底有哪些区别;
理解回调函数,是理解异步编程模型async/await的基础。

什么是回调函数?

其实回调函数也是个函数,它具有函数的所有特征,它可以有参数和返回值。回调函数区别于普通函数,在于它的调用方式。只有当某个函数被作为参数,传递给另外一个函数,或者传递给宿主环境,然后该函数在函数内部或者在宿主环境中被调用,我们才称为回调函数。
具体地讲,回调函数有两种不同的形式,同步回调和异步回调。通常,我们需要将回调函数传入给另外一个执行函数,那么同步回调和异步回调的最大区别在于同步回调函数是在执行函数内部被执行的,而异步回调函数是在执行函数外部被执行的
同步回调:举例 调用函数作为参数
异步回调:举例 setTimeoutUI

线程的宏观架构

UI线程:是指运行窗口的线程,当你运行一个窗口时,无论该页面是Windows上的窗口系统,还是Android或者iOS上的窗口系统。
在页面线程中,当一个事件被触发时,比如用户使用鼠标点击了页面,系统需要将该事件提交给UI线程来处理。
在大部分情况下,UI线程并不能立即响应和处理这些事件。
针对这种情况,我们为UI线程提供一个消息队列,并将这些待执行的事件添加到消息队列
中,然后UI线程会不断循环地从消息队列中取出事件、执行事件。

可以用一段JavaScript代码来模拟下这个过程:

function UIMainThread() {
    while (queue.waitForMessage()) {
        Task task = queue.getNext()
        processNextMessage(task)
    }
}

异步回调函数的调用时机

理解了UI线程的基础架构模型,下面我们就可以来解释下异步函数的执行时机了。

比如在页面主线程中正在执行A任务,在执行A任务的过程中调用setTimeout(foo, 3000),在执行setTimeout函数的过程中,宿主就会将foo函数封装成一个事件,并添加到消息队列中,然后setTimeout函数执行结束。
主线程会不间断地从消息队列中取出新的任务,执行新的任务,等到时机合适,便取出setTimeout设置的foo函数的回调的任务,然后就可以直接执行foo函数的调用了。
那么下面我们就来分析下XMLHttpRequest是怎么触发回调函数的?具体流程你可以参看下图:

结合上图,我们就可以来分析下通用的UI线程是如何处理下载事件的,大致可以分为以下几步:

  1. UI线程会从消息队列中取出一个任务,并分析该任务。
  2. 分析过程中发现该任务是一个下载请求,那么主线程就会将该任务交给网络线程去执行。
  3. 网络线程接到请求之后,便会和服务器端建立连接,并发出下载请求;
  4. 网络线程不断地收到服务器端传过来的数据;
  5. 网络线程每次接收到数据时,都会将设置的回调函数和返回的数据信息,如大小、返回了多少字节、返回的数据在内存中存放的位置等信息封装成一个新的事件,并将该事件放到消息队列中;
  6. UI线程继续循环地读取消息队列中的事件,如果是下载状态的事件,那么UI线程会执行回调函数,程序员便可以在回调函数内部编写更新下载进度的状态的代码;
  7. 直到最后接收到下载结束事件,UI线程会显示该页面下载完成。

总结

同步回调和异步回调,同步回调函数是在执行函数内部被执行的,而异步回调函数是在执行函数外部被执行的。
UI线程宏观架构
UI线程提供一个消息队列,并将待执行的事件添加到消息队列中,然后UI线程会不断循环地从消息队列中取出事件、执行事件。
关于异步回调,这里也有两种不同的类型,其典型代表是setTimeout和XMLHttpRequest。

此文章为5月Day23学习笔记,内容来源于极客时间《图解 Google V8》,日拱一卒,每天进步一点点💪💪

豪猪
4 声望4 粉丝

undefined