跳转至

V8是如何实现闭包的

JavaScript闭包

闭包指的是一个函数,且这个函数绑定了词法环境。通俗点讲就是可以在一个内层函数中访问到外层作用域

惰性加载

V8不会一次性的将所有的JavaScript代码解析为中间代码,主要基于以下两点

  • 如果要将所有的代码一次性解析编译完成,这会严重影响到首次执行JavaScript代码的速度,会大大增加用户的等待时间
  • 其次,解析完成的字节码和编译之后的机器代码都会存放在内存中,如果一次性解析和编译所有JavaScript代码,那么这些中间代码和机器代码就会一直占用内存

惰性解析

function foo(a,b){
    var d = 100
    var f = 10
    return d+f+a+b;
}

对如上一个函数进行存储的时候会像下面的图片一样,遇到一个函数时会将其转换为一个函数对象,并不会处理具体的函数内容

只有在调用函数的时候才会执行操作将函数体中的code转换为AST对象,然后再转换为中间代码

JavaScript中闭包基础特性

JavaScript闭包相关的三个重要特性:

  • 可以在JavaScript函数内部定义新的函数
  • 内部函数可以访问父函数中定义的变量
  • 函数是一等公民,所以函数可以作为另外一个函数的返回值

在JavaScript中函数也是一个对象

执行上下文

执行上下文是用来跟踪记录代码运行时环境的抽象概念。每一次代码运行都至少会生成一个执行上下文

执行上下文有如下两种类型:

  • 全局执行上下文:当运行代码是处于全局作用域内,则会生成全局执行上下文,这也是程序最基础的执行上下文
  • 函数执行上下文:当调用函数时,都会为函数调用创建一个新的执行上下文

调用栈

有了执行上下文就要有一个合理管理它的工具--调用栈。调用栈是一种数据结构,用于管理在主线程上执行的函数的调用关系,是一个LIFO(先进后出)的栈

执行流程图

在一个函数执行结束后,其上下文是会被销毁的,在compile函数执行结束后,parse函数还没执行结束,但是parse函数用到了compile函数中的变量n,所以compile函数执行结束后再调用parse函数会出现问题

V8中如何处理:

引用了预解析器

  • 判断当前函数是不是存在一些语法上的错误
  • 检查函数内部是否引用了外部变量

当检查到函数内部使用了外部变量的时候,会将域中的变量复制到内存中,这样就避免了因为闭包导致的问题