第4章 变量、作用域和内存
4.1 原始值和引用值
ECMAScript变量可以包含两种不同类型的数据:原始值和引用值
-
原始值就是最简单的数据
- 保存原始值的变量是按值访问的
-
引用值则是由多个值构成的对象
- 是保存在内存中的对象
- JavaScript不允许直接访问内存位置,所以不能直接操作对象所在的内存空间
- 操作对象时,实际上操作的是对该对象的引用而非实际的对象本身。
- 保存引用值的变量是按引用访问的
4.1.1 动态属性
- 对于引用值而言,可以随时添加、修改和删除其属性和方法
- 原始值不能有属性
- 只有引用值可以动态添加后面可以使用的属性
4.1.2 复制值
- 原始值会被复制到新变量的位置
- 在把引用值从一个变量赋给另一个变量时,存储在变量中的值(指针,指向存储在堆内存中的对象)复制到新变量所在的位置。两个变量实际上指向同一个对象
4.1.3 传递参数
4.1.4 确定类型
-
typeof操作符
- 适合用来判断一个变量是否为原始类型
- 对引用值用处不大,更关心引用值是什么类型的对象
-
instanceof操作符
-
result = variable instanceof constructor
- 例: console.log(person instanceof Object);
//变量person是Object吗? - 例: console.log(colors instanceof Array);
//变量colors是Array吗?
- 例: console.log(person instanceof Object);
-
4.2 执行上下文与作用域
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。
变量对象
- 每个上下文都有一个关联的变量对象
- 这个上下文中定义的所有变量和函数都存在于这个对象上
- 无法通过代码访问变量对象,但后台处理数据时会用到
全局上下文
-
window对象
- 所以所有通过var定义的全局变量和函数都会成为window对象的属性和方法
-
全局上下文的变量对象始终是作用域链的最后一个变量对象
函数上下文
- 每个函数调用都有自己的上下文
- 代码执行流进入函数时,函数的上下文被推到一个上下文栈中
- 函数执行完之后,该函数上下文从上下文栈中弹出
作用域链
-
上下文中的代码执行时,会创建变量对象的作用域链
-
作用域链决定各级上下文中的代码在访问变量和函数时的顺序
-
正在执行的代码始终位于作用域链最前端
-
函数执行上下文的作用域链
- 变量对象是其活动对象
- 活动对象最初只有一个定义变量:arguments
- 下一个变量对象来自包含上下文
-
代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名完成的。
内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。
4.2.1 作用域链增强
-
增强作用域链的两种情况
-
try/catch语句的catch块
- 向作用域链前端添加指定的对象
-
with语句
- 创建一个新的变量对象,这个变量对象包含要抛出的错误对象的声明
-
4.2.2 变量声明
-
- 使用var的函数作用域声明
- 变量会被自动添加到最接近的上下文
- var声明会被拿到函数或者全局作用域的顶部,位于所有作用域中所有代码之前
- 提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用
-
- 使用let的块级作用域声明
- 块级作用域由最近的一堆包含花括号{}界定
- 在同一作用域内不能重复声明
- let非常适合在循环中声明迭代变量
- 严格来讲,let在JavaScript运行时也会被提升,但由于“暂时性死区”,实际上不能再声明之前使用let变量
-
- 使用const的常量声明
-
使用const声明的变量必须同时初始化为某个值
-
一经声明,在其生命周期的任何时候都不能再重新赋予新值
-
const声明只应用到顶级原语或者对象
-
赋值为对象的const变量不能再被重新赋值为其他引用值,但对象的键不受限制
-
Object.freeze()
- 整个对象都不能修改
-
- 标识符查找
- 搜索开始于作用域前端,以给定的名称搜索对应的标识符。
- 如果在局部上下文搜索到该标识符,则搜索停止,变量确定;如果没有找到变量名,则继续沿作用域链搜索
- 此过程一直持续到搜索至全局上下文的变量对象,如果仍未找到,则说明其未声明
4.3 垃圾回收
JavaScript是使用垃圾回收的语言
JavaScript通过自动内存管理实现内存分配和闲置资源回收
垃圾回收程序每个一定时间自动运行
4.3.1 标记清理
-
JavaScript最常用的垃圾回收策略
-
步骤
- 先标记内存中存储的所有变量
- 将所有在上下文中的变量,以及被上下文中的变量引用的变量的标记去掉
- 此时有标记的就是待删除的,因为它们不在任何上下文中被访问
- 垃圾回收程序进行内存清理,销毁带标记的所有值并收回它们的内存
4.3.2 引用计数
- 对每个值都记录它被引用的次数
- 严重bug:循环引用
4.3.3 性能
- 现代垃圾回收程序会基于对JavaScript运行时环境的探测来决定何时运行
4.3.4 内存管理
- 将内存占用量保持在一个较小的值可以让页面性能更好
- 优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据
- 解除引用:如果数据不再必要,那么把它设置成null,从而释放其引用
- 通过const和let声明提升性能
- 有助于改进垃圾回收的过程
- 可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存
- 隐藏类和删除操作
- Chrome V8JavaScript引擎
- V8在将解释后的JavaScript代码编译为实际的机器码时会利用“隐藏类”
- 内存泄露
- 大部分是由于不合理的引用
- 静态分配与对象池
- 减少浏览器执行垃圾回收的次数