JS-V8引擎的闭包优化-秒懂

519 阅读4分钟

这篇文章承接JS-图解闭包-秒懂

上篇文章中讲到闭包的缺点就是内存泄漏

因为这个Closure是跟着inner函数体的,只要inner函数体一直存在,这个Closure就一直存在。那如何解放这个Closure呢,当没有一个引用指向inner函数的时候,inner函数就会被销毁,Closure就会得到释放啦

所以,《高程》在闭包章节的结尾处又写到:因为背包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用背包可能导致内存的过度占用,因此建议仅在十分必要的时候使用。V8等优化的JavaScript引擎会努力回收被闭包困住了内存。不过我们还是建议在使用背包的时候要谨慎。

接下来要谈论的内容,就和V8的优化有关

其实到这里有两个问题。不知道你发现了没有。这个问题ECMAScript标准是V8实现不同导致的

  1. ECMAScript的标准是只要内部函数被声明了,内部函数闭包就产生了。和内部函数是否引用了外部函数无关
  2. 而V8引擎的实现却不是这样的

看看V8是如何优化闭包的

下面这个代码,outer的变量对象上有变量a和函数inner,而内部函数只引用了变量a

可以看到生成的闭包对象中,只有变量a,没有了inner函数

再看一个代码,这里内部函数只引用了inner,没有引用变量a

可以看到,生成的闭包对象中,只有函数inner,没有了变量a

再看一个代码,内部函数同时引用了变量a和函数inner

可以看到,生成的闭包对象中,既有函数inner,也有变量a

这个就是V8引擎做出的闭包内存优化,让生成的闭包中只含有内部函数引用的变量

如果内部函数根本就没有引用外部函数作用域中的变量,这个Closure还会生成吗?

我们来看代码。下面代码中,inner函数没有引用任何的外部变量。看看打印结果是什么?

这个结果也是在意料之中的,因为没有引用任何的外部变量,所以Closure就根本没有生成

这些V8引擎所做出的优化,我们可以借助这一优化特性,来继续优化我们的闭包大小

引用外部变量的时候,尽量使引用值空间占用最小化

下面的代码中,inner函数打印person中的name属性

我在第7行打了一个断点,代码运行到这里停止。我们可以看在这个状态下的作用域链。如下图右侧所示:

  • Scope表示的是当前执行上下文的作用域链。其中Local,表示当前作用域;Closure表示闭包;Global表示全局的变量对象
  • Closure中有一个变量:person对象

如果这个闭包一直存在的话,那么堆中就必须要保留整个person的对象,而我们只需要name这个属性的值,age属性是不需要的。所以造成了空间的浪费

下面我们对代码做下优化。

将要打印的person.name赋值给一个非引用型变量name,然后inner函数内部再去访问这个name

我们可以看到,在Closure中,只有name这一个属性了。而不再是person了。用什么就存什么,不要内存的浪费😁

关于闭包的优化还涉及到尾递归调用,虽说没有浏览器实现它,但研究这个东西还是很有趣的。

下篇文章就会写这方面的内容,一起来看看吧

总结

这篇文章主要讲了V8引擎中是如何做闭包内存优化的

  1. V8引擎做出的闭包内存优化,让生成的闭包中只含有内部函数引用的变量
  2. 内部函数没有引用任何的外部变量,Closure就不会生成
  3. 引用外部变量的时候,尽量使引用值空间占用最小化
  4. 如果这篇文章中又让你不明白的地方,一定要告诉我,谢谢🙏