JavaScript
是弱类型语言,变量没有限定类型。
原始值和引用值
- 原始值:就是简单值,
Undefined
、Null
、Boolean
、Number
、String
、Symbol
、BigInt
,是按值访问的,数据存储在栈内存中的。 - 引用值:就是多个值构成的对象。操作对象时是对对象的引用,而不是其本身。对象的引用存在栈内存中,对象本身存储在堆内存中的。
动态属性
原始值不能添加属性,只能通过字面量进行初始化。
引用值可以动态添加属性,通过new
或 {}
, []
等来创建。
复制属性
原始值复制的是一个值的副本。
引用值复制的是一个引用的副本。
传递值
ECMAScript
中函数参数都是按值传递的。简单值是值副本,引用值是引用副本。
函数内部通过值传递改变不了外部的原始值。
确定类型
typeof
、instanceof
、Object.prototype.toString.call()
执行上下文和作用域
每个上下文都有一个关联的变量对象。这个上下文定义的所有变量、函数都在这个对象上。
全局上下文就是最外层的上下文,globalThis
对象。
每个函数都有自己的上下文,当代码执行流被推入函数时,函数的上下文会被推到一个上下文栈中,当函数执行完后,这个上下文栈就会弹出函数上下文,把控制权返回之前的执行上下文。
上下文的代码在执行时会创建变量对象的一个作用域链,决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文始终位于作用域链的最前端。
函数的活动对象被用于变量对象,最初只有一个定义变量:argument
。作用域链的下一个变量对象来自包含上下文,再下一个变量对象来自下一个包含上下文,直到全局上下文。
代码在执行过程中标识符的解析是从作用域的前端开始,逐级向上直到全局作用域。
作用域链增强
执行上下文:全局上下文、函数上下文、eval
调用内部存在的第三种上下文。
作用域前端添加一个变量对象:
-
try-catch
的catch
语句。 -
with
语句
变量声明
let
、const
、var
垃圾回收
JavaScript是使用垃圾回收的语言,执行环境负责在代码执行时管理内存。通过自动内存管理实现内存分配和闲置资源的回收。
基本思路:确定哪个变量不会再被使用,然后释放它占用的内存,这个过程是周期性的,每隔一段时间就会自动运行一次。
垃圾回收机制是一个近似且不完美的方案,因为某块内存是否还有用,属于不可判定的,意味着靠算法是解决不了的。
两种标记策略:
- 标记清理
- 引用计数
标记清理
JavaScript
常用的垃圾回收策略。
变量离开上下文时,被标记离开上下文。就会清理掉加上标记的变量。
引用计数
每个值都记录它被引用的次数。声明变量并给他赋值一个引用值时,引用次数加1
。若同一个值又被赋给另一个变量时,那么引用数加一。若保存
其值的变量被其他覆盖,那么引用数减一,当引用次数为0
时,就没办法再访问这个值了,它就可以被回收了。
问题:循环引用导致无法为0
,无法回收。
在<=IE8的IE中,并非所有的对象都是JavaScript
对象。BOM
和DOM
中的对象时C++实现的组件对象模型(COM
)对象,而这个COM
采用的就是
引用计数。
解决办法就是把不用的引用值重新赋值为null
。
性能
垃圾回收程序是周期运行的,若内存中分配了很多变量,造成性能损失,因此垃圾回收机制的循环时间很重要。
要做到:无论何时开始收集垃圾,都让它尽快回收。
内存管理
JavaScript
开发者无需考虑关系内存管理。
环境的不同分配给JavaScript
程序的内存也不同。也是为了避免大量的JavaScript
堆内存过度消耗导致系统卡顿。
优化内存占用:保证在执行代码时只保存必要的数据,若数据不用了将它置为null
,解除引用。
let
、const
提升性能,块级作用域- 隐藏类和避免删除操作
尽量在类中添加属性和方法,不要在实例化后增加属性。
不用的值设置为null
来代替delete
操作。 - 内存泄露
多是函数调用多次导致,不合理的引用导致的。
- 意外声明的全局变量
- 定时器 还在定时器中使用闭包。
-
JavaScript
闭包
- 静态分配与对象池
间接控制垃圾回收触发的条件。避免多余的垃圾回收,就可以保住因为释放内存而导致的损失的性能。
- 静态分配
不在函数中new
一个对象。函数外部new
一个对象然后通过参数传入。 - 对象池:极端优化。
在初始化的某一时刻,创建一个对象池,来管理一组可回收的对象,应用程序向这个对象池请求一个对象,设置其属性使用它
操作完后再把它还给对象池,由于没有发生对象初始化,垃圾回收探测器就不会发现由对象更替,就不用频繁的运行。 - 创建数组时大小固定。