第4章 变量、作用域与内存
# 原始值与引用值
变量分为 2 种:
原始值也称为简单数据类型,包括 Undefined、Null、Boolean、Number、String 和 Symbol。保存原始值的变量是按值访问的。
引用值也成为复杂数据类型,是保存在内存中的对象,细分为 Object、Array、Function 和 js 内置对象等。保存引用值的变量是按引用访问的。
# 复制值
变量 a 赋值给另一个变量 b 时,需要分情况讨论
1.如果变量 a 是原始值,则原始值会复制一份到新变量上,此时对 a 和 b 的修改互不影响。
let a = 5
let b = a
a++
b--
console.log(a, b) // 6 4
2
3
4
5
2.如果变量 a 引用值,则复制的 a 的引用地址,这个地址其实是指针,它指向存储在堆内存中的对象,因此实际上这 2 个变量指向的是同一个对象,此时修改 a 或 b 会同步在另一个变量反映出来。
let a = { name: 'jack' }
let b = a
b.name = 'rose'
console.log(a) // 'rose'
a.name = 'mike'
console.log(b) // 'mike'
2
3
4
5
6
# 函数参数传递
所有函数的参数都是按值传递的,而不是按引用传递。
如果参数是按引用传递,则会认为传入的参数在函数内部被修改后,会影响到外部的变量。而实际上,函数参数的传递,可以理解为就是局部创建了新的变量,用来接收函数外部传入的值。如果这个值是原始值,则是将值复制;如果是引用值,则复制的是内存地址。用法同上一个小结【复制值】。
let a = { name: 'jack' }
function setName(obj, name) {
obj.name = name
}
setName(a, 'rose')
console.log(a.name) // 'rose'
2
3
4
5
6
# 执行上下文与作用域
执行上下文决定了访问数据的方式和行为,体现为如何搜索变量和当前执行环境的 this 代表什么(这一节没讲 this)。
执行上下文包括 2 个方面的内容:变量对象和作用域。(this 也是一部分,但本书没罗列进来)
变量对象:包括上下文中的函数和变量。在当前上下文中,当里面的代码执行完后,变量对象会被销毁。
每次调用函数时,当前函数就会推到一个上下文栈,并执行该函数;函数执行完后上下文栈会弹出该函数上下文。这个过程中函数所在的上下文是一段作用域,这样形成了作用域嵌套作用域的关系,它决定了各级上下文如何访问变量和函数。也称为变量搜索原则,如下:
访问一个变量时,从当前作用域中搜索,如果没有,则逐级向外层作用域中查找,直到全局变量。反过来则不行,即作用域链访问顺序只能从下级查找上级作用域,而不能反过来查找。
# 垃圾回收
js 不需要开发者手动管理内存,而是通过自动内存管理实现内存分配和闲置资源回收。过程是周期性的:确定哪个变量不再使用,则释放它占用的内存。
如何标记未使用的变量,包括 2 类:标记清理和引用计数。
# 标记清理
当变量进入上下文,则标记为存在上下文中。
当变量离开上下文,则标记为离开上下文。
在上下文中的变量,访问权限仅限在本上下文,因此当上下文执行完时,这部分变量都不可被外界访问到了,会带上待删除的标记,随后被垃圾回收程序清理。
# 引用计数
思路是对每个值都记录它被引用的次数,比如声明一个变量,并对其赋值,则该值的引用数为 1;如果该变量指向了另一个值,那前一个值的引用数减 1。引用数为 0 的值,会被垃圾回收程序在下一次运行时释放该值的内存。
问题:循环引用时,引用数永远不会变为 0,会造成内存泄漏。解决方法是,在合适的时机,赋值为 null,即切断变量和值的关系。
浏览器中,垃圾回收的频率会影响渲染速度和帧速率,降低性能。频率高,则影响渲染性能;频率低,则内存不够用。
局部变量在超出作用域后(如上下文执行完毕后)会自动解除引用;而全局变量需要手动解除,让其指向 null。
# 内存泄漏
原因可能如下:
1.意外声明全局变量,比如没有关键字声明变量
function setName() {
name = 'jack'
}
2
3
2.定时器未注销
3.闭包
# 静态分配与对象池
尽量避免一次性实例化多个对象,如果可以,在函数调用之前就实例化,并将实例作为参数传入。即避免动态创建对象,让它直接使用已有的对象。
此外,如果参数多,则使用对象池,即使用一组对象的属性来管理,程序可以向该对象池申请一个对象,在操作完成后再还给对象池。这样避免了频繁初始化对象,也就绕过了垃圾回收程序的检测。