JavaScript高级程序设计4 第四章 变量、作用域与内存
原始值与引用值
引用值是保存在内存中的对象,与其他语言不通,JavaScript不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用而不是非实际对象本身。为此,保存引用值得变量是按引用访问的。
动态属性
- 只有引用值可以动态添加后面可以使用的属性
- 原始类型的初始化可以只使用原始字面量形式。如果使用的是new关键字,则JavaScript会创建一个Object类型的实例,但其行为类似原始值。
复制值
把引用值从一个变量赋给另一个变量时,存储在变量的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,他指向在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化在另一个对象上反映出来。
传递参数
函数的参数都是按值传递的
确定类型
- typeof用来判断一个变量是否为原始类型。
- 可以使用 instanceof 进一步判断
执行上下文与作用域
- 变量或函数的上下文决定了他们可以访问哪些数据,以及他们的行为,每个上下文都有一个关联的变量对象
- 全局上下文是最外层的上下文。
- 在浏览器中,全局上下文就是我们常说的window对象,因此通过var定义的全局变量和函数会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域链解析效果是一样的。
- 上下文在其所有代码执行完毕会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)
作用域链增强
try/catch语句的catch块、with语句,会在作用域链前端添加一个变量对象。
变量声明
- 使用var声明变量时,会将变量自动添加到最接近的上下文
- 如果变量未经声明,就被初始化了,那么他会自动被添加到全局上下文
- var声明会被拿到函数或全局作用域顶部,位于作用域中所有代码之前,这个现象叫做提升,提升让同一作用域中的代码不必考虑变量是否声明就可以直接使用。
- let声明的变量作用域是块级别的,块级作用域由最近的一对{}界定。
- let同一作用域不能重复声明,而重复的var声明则会被忽略
- 使用const声明的变量必须同时初始化某个值。一经声明,在其声明周期的任何时候都不能再重新赋值。
- const声明只应用到顶级原语或者对象,赋值为对象的const变量不能再被重新赋值为其他引用值,但对象的健则不受限制。
- 可以使用Object.freeze()赋值使整个对象都不能修改。
- const声明暗示变量的值是单一类型且不可修改,JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。
- 在特定上下文中为读取或写入而引用一个标识符时,必须通过搜索确定这个标识符表示什么,搜索开始于作用域前端,已给定的名称搜索对应的标识符。如果在局部上下文忠找到该标识符则搜索停止,变量确定;如果没有找到变量名,则继续沿作用域链搜索。这个过程一直持续到搜索至全局上下文的变量对象。如果仍然没有找到标识符,则说明其未声明。
- 对这个搜索过程而言,引用局部变量会让搜索自动停止,而不继续搜索下一级变量对象。也就是说如果局部上下文中有一个同名的标识符,那就不能在该上下文中引用父上下文中同名标识符。
垃圾回收
标记清理
垃圾回收程序运行的时候,会标记内存中存储的所有变量(标记方法有多种)。然后,他会将所有在上下文中的变量,以及被上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到他们了。随后垃圾回收程序做一次内存清理。
引用计数
引用计数( reference counting)。其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存
内存管理
- 将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是在保证执行代码时只保存必要的数据。
- 解除对一个值的应用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不再上下文里了,因此它会在下次垃圾回收时会被回收。
- 浏览器决定合适运行垃圾回收程序的一个标准就是对象更替的速度。
内存泄漏
- 意外声明全局变量
- 定时器也可能会悄悄导致内存泄漏
- 使用JavaScript闭包很容易在不知不觉间造成内存泄漏