[浏览器工作原理]--JavaScript 的内存机制

489 阅读4分钟

1.语言类型

静态语言 VS 动态语言

静态语言,在使用变量之前,需要确认变量的数据类型;

相反,动态语言在运行过程中需要检查数据类型

强类型语言 VS弱类型语言

支持隐式类型转换的语言称为弱类型语言

相反,不支持的称为强类型语言

JavaScript语言

(1)JavaScript 是一种弱类型的、动态的语言

弱类型,意味着你不需要告诉 JavaScript 引擎变量是什么数据类型,JavaScript 引擎在运行时会计算出来

动态,意味着你可以使用同一个变量保存不同类型的数据

(2)JavaScript有8 种数据类型,分为原始类型和引用类型

如果想要查看变量的类型,可以使用“typeof”运算。但是,typeof 检测 Null 时,返回的是 Object。这是当初 JavaScript的Bug

2.内存空间

1. 内存空间类型

在 JavaScript 的执行过程中, 主要有代码空间、栈空间和堆空间三种类型内存空间

2. 存储的数据类型

原始类型的数据值保存在**“栈”中的,引用类型的值是存放在“堆”**中

(1)因为 JavaScript 引擎需要用栈来管理执行上下文的状态, 如果栈空间大了,会影响到上下文切换的效率,进而又影响到整个程序的执行效率,所以栈主要用来存放一些原始类型的小数据

(2)而堆空间很大,引用类型的数据占用的空间都比较大,会被存放到堆中,不过缺点是分配和回收内存都会占用一定的时间

3. 变量赋值

原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址,堆中的数据是通过引用和变量关联起来的。也就是说,JavaScript 的变量是没有数据类型的,值才有数据类型,变量可以随时持有任何类型的数据

3. 闭包的内存分配与回收

function foo() { 
    var myName = " 你的时间 " 
    let test1 = 1 
    const test2 = 2 
    var innerBar = { 
        setName:function(newName){ 
            myName = newName 
        }, 
        getName:function(){ 
            console.log(test1) 
            return myName 
        }
    } 
    return innerBar 
} 
var bar = foo() 
bar.setName(" 你的名字 ") 
bar.getName() 
console.log(bar.getName()) 
//1 
//"你的名字"

当 foo 函数的执行上下文销毁时,由于 foo 函数产生了闭包,所以变量 myName 和 test1 并没有被销毁,而是保存在内存中,那么应该如何解释这个现象呢?

站在内存模型的角度,分析这段代码的执行流程:

  1. 首先,执行js代码前会先编译再运行,因此会先创建全局的执行上下文;

  2. 接着,当调用foo 函数时,会编译并创建函数的执行上下文;

  3. 在编译过程中,遇到内部函数setNamegetName,JavaScript 引擎会进行词法扫描,发现内部函数引用了外部函数的变量,判断是一个闭包,于是在堆空间创建换一个**“closure(foo)”的对象**(这是一个内部对象,JavaScript 是无法访问的),用来保存引用的****变量myName和test1,此时这些被引用的变量的集合就形成了闭包;

  4. 由于test2并没有被内部函数引用,所以test2依然保存在调用栈中;

wps6.jpg

那是如何做到函数执行上下文弹出后,还可以访问原来函数的变量?

当 foo 函数执行结束之后,返回的getName和setName方法都引用“clourse(foo)”对象,所以在下次调用bar.setName或者bar.getName时,创建的执行上下文中就包含了“clourse(foo)”,通过这个对象就可以访问原来函数的变量了

wps7.jpg

总的来说,产生闭包的核心有两步:第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中

还有一个问题,闭包是怎么回收的?

通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;如果这个闭包以后不再使用的话,就会造成内存泄漏;

如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存;

所以在使用闭包尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大,那就尽量让它成为一个局部变量