关于变量、作用域和内存问题,以前零零散散的看别人博文研究过,不过今天自己看到原书,有种醍醐灌顶的感觉,特此记录。
一、变量
前面也提及过变量存在两大分类,基本数据类型和引用类型(关于具体是那些,我就不在此一一陈述了)。同样也说到过,基本数据类型和引用类型的名称变量(指向堆里面所对应的引用类型值的地址)是储存在栈中的,引用类型的真实值是储存在堆中的。
1、那么为什么前者储存在栈中,后者储存在堆中?
这是存储在栈中的都是些变量或者函数的参数,由于他们的类型从而确定了他们所需要的储存空间;而在堆中,储存的是引用类型值,其所占内存大小是不确定的。
2、参数传递形式
在ECMAScript中所有函数的参数传递都是按值传递的。也就是说当你传递参数类型是基本类型,那就相当于把值拷贝了一份;然而当你传递参数类型是引用类型,那就是仅仅将指向它的地址拷贝了一份。比如:
// 参数按值传参
let basicVarible = 1
let obj = {
a: 'a'
}
const test = (basicVarible, obj) => {
console.log(basicVarible) // 1
console.log(obj) // {a: "a"}
basicVarible = 2
obj.a = 'onchange'
}
test(basicVarible, obj)
console.log(basicVarible) // 1
console.log(obj) // {a: "onchange"}
二、执行环境及作用域
1、执行环境
通俗来讲就是你代码所执行的环境(就好像你现在所处的环境在中国)。它定义了你的变量和函数能访问哪些数据;当你所处的执行环境执行完毕,就会销毁执行完毕的执行环境和其所对应的变量对象。
2、变量对象
每个执行环境都有各自的一个变量对象,储存着所对应执行环境内声明的变量和函数。
3、全局执行环境
最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,全局执行环境也不同。在web浏览器中,全局执行环境被认为是window对象。当应用程序退出、关闭网页或浏览器时,全局执行环境才会表示执行完毕,从而销毁。
4、环境栈【存储类型类同栈结构】
储存着正在执行的作用域(这就代表着在应用程序退出、关闭网页或浏览器之前,全局作用域始终在栈底)。每当执行流进入到一个新的作用域(比如:开始执行某个函数),就会将该作用域推入到环境栈中;当执行完毕后,则撤出环境栈。
5、作用域链
链接着环境栈中作用域所对应的变量对象,最开始的一端是你正在执行的作用域的变量对象。这就代表着,作用域它定义了你的变量和函数能访问哪些数据,而怎么访问的则是通过你作用域中的变量对象所处的作用域链。
三、垃圾收集
Javascript具有垃圾收集机制,也就是说,执行环境会负责代码执行过程中使用到的内存。关于怎么处理垃圾回收的,现在目前只有两种方式,标记清除和引用计数。
1、标记清除
标记清除是Javascript最常用的的垃圾清除方式。当变量进入环境(例如:在函数中声明一个变量,若没有赋值时,默认赋值为undefined
,这并不是null
,就说明不会触发解除引用从而被标注为离开环境,因此同样也代表着此变量状态为进入环境)时,就将此变量标记为进入环境;当变量被解触引用时(手动解触引用是给此变量赋值null
),这表明没有变量可以访问这些变量了,因此会标记为离开状态。
垃圾收集器在运行时会给所有内存中的变量打上个标注,然后会将状态标为进入环境的变量取消开始运行时打上的标注,最后会将存在开始运行时打上的标注的变量视为待清除变量。
2、引用计数
这是种不太常见的垃圾清理机制(快被淘汰)
2.1、声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
2.2、同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
2.3、当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
2.4、当引用次数变成0时,说明没办法访问这个值了。
2.5、当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
缺点:循环引用,内存得不到释放
四、内存管理
值得注意的一点(个人认为,因为所疏忽的一点),关于解除引用。局部变量随着执行环境的销毁而自动被解除引用,当然你也可以手动的将变量赋值为null
,从而使对应的变量被解除引用。
五、关于闭包简单说下
// 闭包--通过return一个函数体,将原先函数内部数据带离到外部
// 使得原先函数作用域即使被销毁,然而其原型链上的变量对象无法被释放
// 缺点:会造成内存泄露
const test = function() {
var count = 0
return function() {
console.log(this)
count++
return count
}
}
const wrap = function() {
var add = test()
return add
}
const count = wrap()
console.log(count())
console.log(count())
console.log(count())