第四章:变量、作用域与内存
4.1原始值与引用值
Undefined、Null、Boolean、Number、String 和 Symbol原始值,保存原始值的变量是按值访问的,因为我们操作的就是存储在变量中的实际值。存在栈内存上.
引用值是保存在内存中的对象。实际上操作的是对该对象的引用而非实际的对象本身。为此,保存引用值的变量是按引用访问的。存在堆内存上
4.1.1 动态属性
- 原始值不能有属性,添加了也不生效、
- 原始类型的初始化只可以使用原始的字面亮形式,如果使用了new关键字,js会创建一个新的Object类型实例;
4.1.2 复制值
- 在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置;
- 在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来.
4.1.3 传递参数
在按值传递参数时,值会被复制到一个局部变量,函数中参数的值改变之后,原始的引用仍然没变。当参数在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了,所以本地变量的修改不会反映到函数外部。
4.1.4 确定类型
- typeof: 字符串、数值、布尔值或 undefined;
- instanceof:操作符检测任何引用值和Object 构造函数都会返回 true,但是可以检测Array、RegExp等;如果用 instanceof 检测原始值,则始终会返回 false,因为原始值不是对象;
- 在直到Safari 5和Chrome 7,检测正则表达式,typeof 操作符在用于检测函数时也会返回"function";在 IE 和 Firefox 中,typeof 对正则表达式返回"object"。
4.2 执行上下文与作用域
- 每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文;
- 上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象用作变量对象。活动对象最初只有一个定义变量:arguments(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象。
4.2.1 作用域链增强
- try/catch 语句的 catch 块:创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明
- with 语句:会向作用域链前端添加指定的对象
4.2.2 变量声明
- 在使用 var 声明变量时,变量会被自动添加到最接近的上下文,如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文(在严格模式下,未经声明就初始化变量会报错);var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前.
- let作用域是块级的(块级作用域由最近的一对包含花括号{}界定),let 与 var 的另一个不同之处是在同一作用域内不能声明两次。重复的 var 声明会被忽略,而重复的 let 声明会抛出 SyntaxError。
- const 声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值.赋值为对象的 const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制.
- 在局部变量中查找,找不到再继续向上查找,知道找到或全局中没有再报错
4.3 垃圾回收
4.3.1 标记清理
1.标记内存中存储的所有变量
2.所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉;
3.在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了
4.3.2 引用计数
对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变
量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存
问题:循环引用
function problem() {
let objectA = new Object();
let objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
通过以下代码可以清除前面的例子中建立的循环引用:
myObject.element = null;
element.someObject = null;
4.3.3 性能
IE7 发布后,JavaScript 引擎的垃圾回收程序被调优为动态改变分配变量、字面量或数组槽位等会触发垃圾回收的阈值。IE7 的起始阈值都与 IE6 的相同。(256 个变量、4096 个对象/数组字面量和数组槽位(slot),或者 64KB 字符串),如果垃圾回收程序回收的内存不到已分配的 15%,这些变量、字面量或数组槽位的阈值就会翻倍。如果有一次回收的内存达到已分配的 85%,则阈值重置为默认值.
4.3.4 内存管理
- 解除引用:如果数据不再必要,那么把它设置为 null,从而释放其引用。
- 多多使用let和const声明提升性能
- 隐藏类和删除操作:如果两个实例共享同一个构造函数和原型,那么这两个类实例共享相同的隐藏类,但是删除和添加属性的话,他们将不再共享一个隐藏类.最佳实践是把不想要的属性设置为 null。这样可以保持隐藏类不变和继续共享或者先创建再补充式的动态属性赋值,并在构造函数中一次性声明所有属性.
- 内存泄漏问题:意外声明全局变量、定时器、必包
- 静态分配(极端形式,不用考虑)与对象池(略)