1、原始值和引用值
- 原始值:最简单的数据
- 按值访问,操作的是存储在变量的实际值
- 引用值:由多个值构成的对象
- 不允许直接访问内存位置,实际操作的是对该对象的引用而非实际对象的本身
- 保存引用值的变量是按引用访问的。
1、动态属性
- 引用值可以随时增删改属性和方法
- 原始值不能有属性,虽然不会报错,但无效
- 原始类型初始化可以值使用原始字面量形式,如果用new会创建Object类型的实例,但行为类似原始值
2、复制值
- 原始值会被复制到新的位置
- 引用值也会把存储的值复制到新变量的位置,但这里复制的是一个指针,实际指向堆中的同一个对象。
3、传递参数
-
函数的参数都是按值传递的,即函数外地值会被复制到函数内部的参数中
-
按值传递参数时,值会被复制到局部变量(arguments的一个槽位)。
-
如果是按引用传递参数,只在内存中的地址会被保存在一个局部变量,意味着对本地变量的修改会反应到函数外部(不可能)
-
原始值显而易见,引用值也是按值传递而不是按引用传递
function setName(obj) { obj.name = 'firstSet' obj = new Object() obj.name = 'secondSet' } let person = new Object() setName(person) persion.name // 'firstSet'(而不是secondSet) -
函数的参数就是局部变量
4、确定类型
- typeof 对原始值很有作用,但对引用值作用不大,返回object
- instanceof 用来知道是什么类型的对象
- instanceof 检测所有引用值和Object构造函数都会返回true
- instanceof 检测所有原始值都会返回false
- typeof 能检测函数
2、执行上下文与作用域
- 上下文决定了可以访问哪些数据,以及它们的行为,每个上下文都有一个关联的变量对象
- 全局上下文是最外层的上下文,浏览器 window对象
- 上下文在其所有代码都执行完毕后会笑会,包括定义在上面的所有变量和函数
- 每个函数都有自己的上下文,执行流进入函数,函数上下文被推到一个上下文栈上。函数执行完毕,上下文栈会弹出函数上下文,将控制权返还给之前的上下文。
- 作用域链,决定各级上下文中的代码在访问变量和函数时的顺序
- 局部作用域定义的变量可用于在局部上下文中替换全局变量。
- 每个上下文都能到上一级上下文中去搜索变量和函数,但任何上下文都不能去下一级上下文中去搜索。
1、作用域链增强
- 增强上下文,导致作用域链前端临时添加一个上下文,执行后会删除
- try/catch语句的catch 创建新的变量对象,包含要抛出的错误
- with 前端添加指定对象
2、变量声明
1、var 函数作用域
- 变量会被自动添加到最接近的上下文,函数中就是函数的局部上下文/
- 如果未经声明就初始化了,那么就会自动添加到全局上下文。
- 提升:var声明会拿到全局或者函数的作用域顶部,位于所有代码之前
2、let 块作用域
- 作用域:块级(最近的一对包含花括号{}界定)
- 同一个作用域中不能声明两次
- 适合循环中声明迭代变量
- “暂时性死区”,不能在声明前使用
3、const 常量
- 声明的同时必须初始化
- 不能重新赋值
- 其他特征等同let
- 只应用到顶级原语或对象(属性可修改),如果想让整个对象都不可修改,可以用Object.freeze()
- 尽量能用const就用const
4、标志符查找
3、垃圾回收
- 自动内存管理实现内存分配和闲置资源回收。
- 周期性释放不再会使用的变量,释放它占用的内存。
- 函数中,局部变量在执行时存在,此时栈或堆内存会分配空间以保存相应的值。函数在内部使用了变量,然后退出。此时,就不需要那个局部变量了,它占用的内存就可以释放。
1、标记清理
- 最常用的垃圾回收策略
- 标记方法
- 垃圾回收机制运行时。标记内存中存储的所有变量。
- 将所有在上下文中的变量,以及被上下文中变量引用的变量的标记去掉
- 在此之后再被加上标记的变量就是待删除的了。
- 随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存
2、引用计数
- 对每个值都记录它被引用的次数。
- 思路
- 声明并赋引用值时,引用数 1
- 同一个值赋给另一个值 引用数 加1
- 被其他值覆盖 引用数 减 1
- 当引用数为0,可以被回收。
- 问题:循环引用
3、性能
- 无论什么时候开始收集垃圾,都尽快让他结束工作
4、内存管理
- 将内存占用量保持较小的值,可以使页面性能更好,不需要的数据设置为null,也叫解除引用。适合全局变量,全局属性。
1、通过const和let声明提升性能
- 块作用域,可以更早让垃圾回收程序介入,尽快回收内存。
- 块作用域比函数作用域更早终止的时候,就可能会发生。
2、隐藏类和删除操作
-
避免先创建再补充,在构造函数中一次性声明所有属性,共享一个隐藏类
function Art() { this.title = 'title' } let a1 = new Art() let a2 = new Art() a2.author = 'author' // bad function Art2(author) { this.title = 'title' this.author = author } let a3 = new Art1() let a4 = new Art1('author') // good -
动态删除属性(delete)和动态添加属性效果一样,可以改为设置为null。
3、内存泄露
- 原因:不合理的引用
- 方式
- 意外的声明全局变量
- 定时器,通过闭包引用外部变量
- 闭包
4、静态分配与对象池
-
间接的控制触发垃圾回收的条件,合理使用分配的内存,避免多余垃圾回收
-
避免动态创建新对象,修改后返回。
function addVetor(a, b) { let resultant = new Vetor() resultant.x = a.x + b.x resultant.y = a.y + b.y return resultant } // bad function addVetor(a, b, resultant) { resultant.x = a.x + b.x resultant.y = a.y + b.y return resultant } // good- 其他地方创建resultant,使用对象池。
-
对象池
- 初始化的某一时刻,创建一个对象池,用来管理一组可回收的对象。
- 对象不存在时创建新的,存在时复用存在的。
- 极端形式,不多见,一般不考虑。