前端基础修炼日志(一):js内存管理机制

  这段时间潜心钻研js底层基础原理,颇有些心得。特别是最近遇到的一个趣事儿,有同学在前端交流群里发了一篇博客,讲的是如何解决for循环中定义的变量与异步方法setTimeout冲突的几种解决方法,原题如下,很简单的一道关于js作用域的面试题。

for (var i = 0; i< 10; i++){
    setTimeout(() => {
        console.log(i);
    }, 1000)
}

  看后我在群里吐槽了下js没有块级作用域的弊端——循环体中定义的变量在循环结束后,在下文中依然能继续使用,实在是反人类的设计!。

  这时候另一个同学跳出来发表了不同的观点,先是从es5规范开始大谈特谈,然后到前端工程化模块化云云,反正就是各种论证当初设计js时没加入块级作用域的好处。说实话我没怎么看懂他到底想要表达什么。但他后面一句话引起了我的兴趣,他原话是这样的:for循环里的变量不需要去关注,因为GC采用的是标记清除法,而全局变量销毁不掉。

  看到这我算是知道了,这哥们怕是对js的内存管理机制和垃圾回收机制存在什么误解。他误解最明显的一个点就是:GC回收的是变量。之所以很多人有这样的误解,是以为变量生命周期结束=内存回收,而全局变量作为最外层的作用域,会一直存在于执行栈中。对于这点,我们看看书上是怎么说的。

  从书上的讲解,我们首先要明白GC回收的是内存空间,而非变量,即便变量的生命周期并未结束。例如我们在全局中定义了一个变量x=3,此时内存中会开辟出一块空间保存3这个值,x相当于一个指向该内存空间的标记。当我们将x赋值为null,那么此时内存中的3脱离了执行环境,并且没有任何变量标记指向它,当js内核的GC执行周期开始后,检测到内存中存在未被引用的3,便对其进行回收,这便是大多数js引擎使用的基于标记清除法的内存管理机制。而js因为没有块级作用域,在循环体内定义的变量依然停留于当前作用域,直到整块作用域生命周期结束,若这段时间内GC回收器触发,会导致这块内存不能及时被gc回收,从而造成内存泄漏。

  而他的另一个误解就是全局作用域、外层作用域,当前执行环境,这三个容易混淆。我们知道js代码是有作用域链的,当一个函数入栈的时候,与该函数相关的作用域有很多层,按包含关系形成了一条作用域链,每次访问变量时总是从内往外逐层去找,直到找到为止,最内层为当前执行环境,最外层为全局。很多视频或者博客在讲解源码的时候为了省事,总是说全局作用域,其实那只是相对当前执行环境的外层作用域而已。而内存回收并不会关心当前作用域还是外层作用域的生命周期,它是一条管理内存的浏览器独立线程。

  后来这位同学在跟我私聊讨论之后,也觉得我说的很对,爽快的承认了自己确实没怎么了解过js的底层原理,对其的理解也只是停留在表面的浅尝辄止和臆测。我也向他推荐了《JavaScript高级程序设计》这本书。可见掌握基础和底层原理对一个开发来说是非常重要的,不单可以让我们在技术攻关时有更多的思路,得出最适合的方案。向深层次发展时更能帮助我们理解得更透彻、深刻,而不至于误入歧途。

原文地址:https://www.cnblogs.com/jiangxiaoxi/p/11749357.html