第4章 变量、作用域与内存
一、原始值与引用值
-
ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值,有以下特点。
- 原始值大小固定,因此保存在栈内存上。
- 从一个变量到另一个变量复制原始值会创建该值的第二个副本。
- 引用值是对象,存储在堆内存上。
- 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
- 从一个变量到另一个变量复制引用值只会复制指针,因此结果是两个变量都指向同一个对象。
typeof操作符可以确定值的原始类型,而instanceof操作符用于确保值的引用类型。
-
保存原始值的变量是按值(by value)访问的,保存引用值的变量是按引用(by reference)访问的。
-
JS 在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。
-
只有引用值可以动态添加后面可以使用的属性。
-
ECMAScript 中所有函数的参数都是按值传递的。
二、执行上下文与作用域
- 在浏览器中,全局上下文就是我们常说的
window对象。 - 所有通过
var定义的全局变量和函数都会成为window对象的属性和方法。 - 内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。上下文之间的连接是线性的、有序的。
- 在初始化变量之前一定要先声明变量。
- 块级作用域由最近的一对包含花括号
{}界定。换句话说,if块、while块、function块,甚至连单独的块也是let声明变量的作用域。 - 使用
var声明的迭代变量会泄漏到循环外部。 - 想让整个对象都不能修改,可以使用
Object.freeze(),这样再给属性赋值时虽然不会报错, 但会静默失败。
const o3 = Object.freeze({});
o3.name = 'Jake';
console.log(o3.name); // undefined
- 在局部变量
color声明之后的任何代码都无法访问全局变量color,除非使用完全限定的写法window.color。
三、垃圾回收
-
JavaScript 是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存,通过自动内存管理实现内存分配和闲置资源回收
-
基本思路是确定哪个变量不会再使用,然后释放它占用的内存。
-
如何标记未使用的变量实现方式有:标记清理(mark-and-sweep)、引用计数(reference counting)。
-
标记清理中,当变量进入上下文,这个变量会被加上存在于上下文中的标记。当变量离开上下文时, 也会被加上离开上下文的标记。垃圾回收程序运行中,先标记所有变量,去掉上下文变量,再加上标记的变量就是带删除的变量,之后程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
-
引用计数的思路是对每个值都记录它被引用的次数,垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。存在循环引用的问题。
-
“在一次完整的垃圾回收之后,V8 的堆增长策略会根据活跃对象的数量外加一些余量来确定何时再次垃圾回收。”
-
优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。
-
解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。
-
V8 会在后台配置,让这两个类实例共享相同的隐藏类。
-
解决方案就是避免 JavaScript 的“先创建再补充”(ready-fire-aim)式的动态属性赋值,并在构造函数中一次性声明所有属性。
-
动态删除属性与动态添加属性导致的后果一样。
-
最佳实践是把不想要的属性设置为
null。这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。 -
JavaScript 中的内存泄漏大部分是由不合理的引用导致的,如。
- 意外声明全局变量。
- 定时器的回调通过闭包引用了外部变量。
- 使用 JavaScript 闭包造成内存泄漏。
-
浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度。