变量、作用域与内存

变量

变量就是特定时间点一个特定值的名称

原始值与引用值

变量包含两种不同类型的数据:

原始值:最简单的数据,包括6种:Undefined、Null、Boolean、Number、String和Symbol
引用值:由多个值构成的对象,是保存在内存中的对象

存储方式不同:

原始值大小固定,保存在栈内存上
引用值是对象,保存在堆内存上

访问方式不同:

保存原始值的变量是按值(by value)访问的
保存引用值的变量是按引用(by reference)访问的

JavaScript不允许直接访问内存位置,实际上操作的是对该对象的引用而非实际的对象本身

属性

原始值不能有属性,尽管给原始值添加属性不会报错,但下一行就会消失
引用值可以随时添加、修改和删除其属性和方法,只有引用值可以动态添加后面可以使用的属性

复制值

变量复制:

把原始值赋值给另一个变量时,原始值会被复制到新变量的位置
把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置,这里复制的值实际上是一个指针,指向存储在堆内存中的对象

传参

所有函数的参数都是按值传递,包括原始类型和引用类型
是深拷贝,把值传递到arguments中

判断类型

1.对原始值:使用typeof操作符来判断一个变量是否为原始类型
可以判断一个变量是否为字符串、数值、布尔值或者undefined,如果值是对象或null,则typeof返回“object”
typeof操作符在用于检测函数时也会返回“function”
2.对引用值:使用instanceof操作符返回true
通过instanceof操作符检测任何引用值和Object构造函数都会返回true
如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象

上下文

每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上
上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数

全局上下文

全局上下文是最外层的上下文,在浏览器中,全局上下文就是常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法
全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器

函数上下文

每个函数调用都有自己的上下文,当代码执行流进入函数时,函数的上下文被推到一个上下文栈上
上下文中的代码在执行的时候,会创建变量对象的一个作用域链,这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序
局部作用域中定义的变量可用于在局部上下文中替换全局变量
内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西

arguments是一个定义变量

增强作用域链

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

try/catch语句的catch块,会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明
with语句,会向作用域链前端添加指定的对象

代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数

变量声明

var声明全局变量

在使用var声明变量时,变量会被自动添加到最接近的上下文
如果变量未经声明就被初始化了,那么他就会自动被添加到全局上下文
var声明有变量提升现象,会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前
变量提升使得在变量声明之前使用变量
注意变量要进行声明和初始化,二者缺一不可,否则都会报错

let声明块级作用域

块级作用域由最近的一对包含花括号{}界定
重复的var声明会被忽略,重复的let声明会抛出SyntaxError
let声明也会有变量提升,但不能在声明之前使用let变量,所以会处于“暂时性死区”

const常量声明

使用const声明的变量必须同时初始化为某个值,一经声明在其生命周期的任何时候都不能再重新赋予新值

标识符查找

先在局部上下文中搜索这个标识符,未找到,再去全局上下文搜索
标识符查找是有代价的,访问局部变量比访问全局变量要快,因为不用切换作用域

垃圾回收

确定哪个变量不会再使用,然后释放它占用的内存
这个过程是周期性的,每隔一定时间就会自动运行

标记清理

当变量离开上下文时,会被加上离开上下文的标记

原理:运行垃圾回收程序时,会标记内存中存储的所有变量,然后它会将所有在上下文中的变量,以及被在上下文中的变量和引用的变量的标记去掉,在此之后再被加上标记的变量就是待删除的,原因是任何在上下文中的变量都访问不到他们了,随后垃圾回收程序做一次内存清理,销毁带标记的所有的值并回收他们的内存

引用计数

思路:对每个值都记录它被引用的次数

原理:声明变量并给它赋一个引用值,则这个值的引用数为1,如果同一个值又被赋给另一个变量,那么引用数加1,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1,当一个值的引用数为0时,就说明没办法再访问到这个值了,垃圾回收程序下次运行的时候就会释放引用数为0的值的内存

引用计数在代码中存在循环引用时会出现问题

内存管理

如果数据不再必要,那么把它设置为null,从而释放其引用,这也叫做解除引用
局部变量在超出作用域后会被自动解除引用

通过const和let声明提升性能

因为const和let都以块为作用域,相比于使用var,可能会更早地让垃圾回收程序介入

隐藏类和删除操作

把不想要的属性设置为null,这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果

内存泄漏

使用JavaScript闭包很容易在不知不觉间造成内存泄漏
创建了一个内部闭包,只要返回的函数存在就不能清理name,因为闭包一直在引用着它

静态分配与对象池

静态分配:对象池只按需分配矢量(在对象不存在时创建新的,在对象存在时则复用存在的),则这个实现本质上是一种贪婪算法,有单调增长但为静态的内存
是合理使用分配的内存,同时避免多余的垃圾回收,就可以保住因释放内存而损失的性能
对象池:在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。
原理:应用程序可以向这个对象池请求一个对象、设置其属性、使用它,然后在操作完成后再把它还给对象池。由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运行。

posted @   Ericup  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示