第五篇《Google V8 原理》之闭包&回调函数

102 阅读3分钟

V8编译器有两个阶段:编译执行

如果一次性解析和编译所有JS代码,会增加编译时间且影响代码执行速度,卡顿感严重,其次解析完成的字节码和编译以后 的机器代码都会存放内存中,内存相当宝贵,所以由于以上原因,JS虚拟机 实现惰性解析

举例:将函数的生命转换为函数对象,但并不解析和编译函数内部的代码,所以也不会生成抽象语法树

闭包

闭包的三个特征

  1. 允许在函数内部定义新的函数
  2. 可以在内部函数中访问父函数中定义的变量
  3. 函数可以作为返回值

总结:

所谓惰性解析是指解析器在解析的过程中,如果遇到函数声明,那么会跳过函数内部的代码,并不会为其生成 AST 和字节码,而仅仅生成顶层代码的 AST 和字节码。

利用惰性解析可以加速 JavaScript 代码的启动速度,如果要将所有的代码一次性解析编译完成,那么会大大增加用户的等待时间。

闭包会引用当前函数作用域之外的变量,所以当 V8 解析一个函数的时候,还需要判断该函数的内部函数是否引用了当前函数内部声明的变量,如果引用了,那么需要将该变量存放到堆中,即便当前函数执行结束之后,也不会释放该变量。

回调函数

什么是回调函数?

某个函数作为参数传递给另外一个函数或者传递给宿主环境。然后该函数在 函数内部或者在宿主环境被调用,称之为回调函数

同步回调函数

同步回调函数是在执行函数内部被执行的

异步回调函数

异步回调函数是在执行函数外部被执行的。

UI线程的宏观架构

UI 线程提供一个消息队列,并将待执行的事件添加到消息队列中,然后 UI 线程会不断循环地从消息队列中取出事件、执行事件

关于异步回调,这里也有两种不同的类型,其典型代表是 setTimeout 和XMLHttpRequest。

setTimeout 的执行流程其实是比较简单的,在 setTimeout 函数内部封装回调消息,并将回调消息添加进消息队列,然后主线程从消息队列中取出回调事件,并执行回调函数。

XMLHttpRequest 稍微复杂一点,因为下载过程需要放到单独的一个线程中去执行,所以执行 XMLHttpRequest.send 的时候,宿主会将实际请求转发给网络线程,然后 send 函数退出,主线程继续执行下面的任务。网络线程在执行下载的过程中,会将一些中间信息和回调函数封装成新的消息,并将其添加进消息队列中,然后主线程从消息队列中取出回调事件,并执行回调函数。