红宝书学习笔记【第4章】

红宝书学习笔记【第4章】

1. 原始值 和 引用值

1.1 复制值

原始值的复制是值的复制.
通过变量把一个原始值复制到另一个变量, 原始值会被复制到新变量的内存位置, 两个值是完全独立的.

引用值的复制是引用的复制.
通过变量把一个引用值复制到另一个变量, 存储在变量中的值也会被复制到新变量的内存位置, 但这个值实际上是一个指针, 它指向存储在堆内存中的对象. 复制后, 两个变量实际指向同一个对象.

1.2 传递参数

es 中所有函数的参数都是按值传递的.
按值传参时, 值会被复制到一个局部变量(arguments对象中的一个槽位).

1.3 确定类型

typeof 操作符适合用来判断一个变量是什么类型的原始变量, 但对于对象或null. typeof 会返回"object".

我们通常更关注一个对象是什么类型的对象.
es 提供了instanceof 操作符, 用来判断对象的类型( 这由其原型链决定 ).

result = variable instanceof constructors

根据定义, 所有引用值都是Object的实例, 所以通过 instanceof操作符检测任何引用值和Object构造函数都会返回true.
用instanceof检测原始值, 始终返回false, 因为原始值不是对象.

2. 执行上下文和作用域

2.1 简介

上下文指代码执行时的环境, 包括变量对象, 作用域链, this指向.

每个上下文都有一个关联的变量对象, 这个上下文中的所有变量和函数都存在于这个对象上.

全局上下文是最外层的上下文, 浏览器中的全局上下文是windows, 通过var定义的全局变量和函数都会成为window对象的属性和方法.

上下文在其所有代码执行结束后, 会连同定义在它上面的所有变量核函数都销毁.
全局上下文在应用程序退出时销毁.

每个函数调用都有自己的上下文, 代码执行流进入函数, 函数的上下文被推到一个上下文栈上. 函数执行结束, 上下文栈会弹出该函数的上下文, 将控制权返还给之前的上下文.

上下文中的代码执行时, 会创建变量对象的一个作用域链.
如果上下文是函数, 其活动对象用作变量对象.
活动对象最初只有一个定义变量: arguments.

2.2 作用域链加强

某些语句会导致在作用域链前端临时添加一个上下文, 这个上下文在代码执行后会被删除.

通常的两种情况:

  • with 语句 (该语句不推荐使用)
  • try/catch 语句中的 catch 块

catch 语句会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。

2.3 标识符查找

在特定上下文中使用一个标识符, 必须通过搜索确定这个标识符表示什么.
搜索开始于作用域链前端, 如果局部上下文中能找到该标识符, 搜索停止, 变量确定. 如果没有, 会继续沿着作用域链搜索, 直到搜索至全局上下文的变量对象, 没搜索到说明没定义.

3. 垃圾回收

js 的执行环境负责在代码执行时管理内存.
垃圾回收是周期性的, 是一个近似且不完美的方案.

3.1 标记清理

当变量进入上下文, 例如在函数内部声明一个变量, 这个变量会被加上存在于上下文中的标记, 变量离开上下文时, 会被加上离开上下文的标记.

(这部分书上写的没看懂, 搜了另外一个文章):

  1. 从全局变量和栈中的局部变量开始, 标记所有可访问的对象. 这些对象被认为"活动的"或"可达的".
  2. 遍历堆内存中的所有对象, 检查对象是否被标记, 没有被标记则认为是"不可达的", 会将其回收.
  3. 清除所有标记, 为下次垃圾回收做准备.

3.2 引用计数

原理: 记录每个值被引用的次数.
声明变量并给它赋值, 这个值的引用数为1. 同一个值被赋给另一个变量时, 该值的引用数+1, 引用该值的变量被赋了其他值时, 该值的引用数-1.
当引用数为0时, 说明已无法再访问到该值, 该值可以被回收了.

存在的问题:
循环引用, 引用数无法变为0, 导致变量无法被回收.

3.3 内存管理

3.3.1 解除引用

如果数据不再必要, 那么把它设置为null, 从而释放其引用.
这对全局变量和全局对象的属性更适用.
但解除对一个值的引用不会自动导致相关内存的回收, 只是确保相关值不再上下文里了, 因此它会在下次垃圾回收是被回收.

3.3.2 通过 const 和 let 提升性能

因为 const 和 let 都已块为作用域, 相比于 var 使用函数为作用域, 使用这两个关键字可能会让垃圾回收更早接入 (块作用域比函数作用域更早终止的情况下).

3.3.3 隐藏类 和 删除操作

隐藏类是 v8引擎内部使用的一种优化技术, 可以提高对象属性的访问和设置的速度.

因为JS对象可以在运行时新增和删除属性, 这一特性使JS对象很灵活, 但这一特性也增加了性能开销.
为了优化性能, V8引擎使用隐藏类来记录对象结构.

// 创建两个对象
let obj1 = { a: 1, b: 2 };
let obj2 = { a: 1, b: 2 };

// 在 V8 引擎中,obj1 和 obj2 可能会共享同一个隐藏类,
// 因为它们的结构相同(具有相同的属性名和属性类型)。

// 添加新属性
obj1.c = 3;

// 在 V8 引擎中,obj1 现在可能有一个新的隐藏类,
// 该隐藏类包含了属性 a、b 和 c 的结构。

// obj2 仍然使用原来的隐藏类,因为它的结构没有改变。

为了更好的使用该特性对代码进行优化, 可采用以下几点:

  1. 创建对象时, 避免"先创建再补充" 式的动态属性赋值.
  2. 减少动态的删除属性(delete), 可以将不想要的属性置为null,
3.3.4 内存泄漏

全局变量 和 闭包问题

3.3.5 静态分配和对象池

减少浏览器执行垃圾回收的次数.

浏览器决定何时运行垃圾回收的一个标准是对象更替的速度.如果有很多对象被初始化,然后一下子又都超出了作用域, name浏览器会采用更激进的方式调度垃圾回收程序运行, 这会影响性能.

策略:
创建一个对象池, 管理一组可回收的对象, 引用程序可以向这个对象池请求一个对象、设置其属性、使用它, 操作完成后将他还给对象池. 因为没有发生对象初始化, 就不会被垃圾回收监测到对象更替.

注:
静态分配是优化的一种极端形式, 属于过早优化, 并不常见. 如果因为垃圾回收严重导致需要提升性能, 可考虑使用这种方案.

你可能感兴趣的:(学习,笔记)