Fork me on GitHub

JavaScript 变量与内存

JavaScript 变量与内存

知其然,知其所以然

变量

根据 ECMA-262 定义,JavaScript 中的变量区别于其他编程语言。JavaScript 中的变量只是用于保存特定值的名称,而变量的值数据类型可在脚本的生命周期改变。
也就是说“变量无类型,有类型的是变量的值

数据类型

JavaScript 中变量值可能包含两种数据类型:基本类型引用类型

基本类型

ES5 中有五种基本数据类型:Undefined Null Boolean Number String,ES6 新增 Symbol
基本类型数据都是保存在中的简单数据,按值来访问的,可直接操作保存在变量中的实际的值
基本类型的值是不允许更改的,当你修改了变量的值,所做的只是创建了新值并复制到了原先变量值的位置

引用类型

引用类型的值为保存在内存中的对象,区别于其他语言,JavaScript 中不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。而实际上对对象的操作是对对象引用的操作。

动态属性

两种类型定义方式类似:创建变量并为变量赋值,但不同类型值可进行的操作却有不同:
我们可以为引用类型数据添加属性和方法,也可改变和删除属性和方法,而对于基本类型是不可以的

1
2
3
4
5
6
7
var a = {}
a.b = 'test'
a.b //test
var a = 1;
a.test = 'test data'
a.test //undefined

变量复制

除了变量存储方式不同,变量的复制也存在着不同

向一个变量复制一个基本类型的值时,会创建新值然后赋值到新变量分配的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a = 1;
var b = a;
复制前的变量对象
________________________________
| | |
| | |
|__________|_____________________|
| | |
| a | 1 |
|__________|_____________________|
___________________________________
复制后的变量对象
________________________________
| | |
| a | 1 |
|__________|_____________________|
| | |
| b | 1 |
|__________|_____________________|

当从一个变量向另一个变量复制引用类型的值时,同样的也会将存储在变量对象中的值复制一份并放置与新变量分配的空间,不同的是值的副本是一个指针,指针指向存储在堆内存的对象。
复制后,两个变量引用同一个对象,即修改一个变量的值也会影响另一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var obj1 = {};
var obj2 = obj1;
obj1.name = 'test';
obj2.name //test
复制前的变量对象 堆内存
________________________________ ________________________________________
| | | | |
| | | | |
|__________|_____________________| | ___________ |
| | | | | | |
| obj1 | object 引用 ---------------------------| object | |
|__________|_____________________| | | |__________| |
| | |
___________________________________ | |_______________________________________|
|
复制后的变量对象 |
________________________________ |
| | | |
| obj1 | object 引用 ----------|
|__________|_____________________| |
| | | |
| obj2 | object 引用 ----------|
|__________|_____________________|

内存管理

JavaScript 具有自动垃圾收集机制,也就是说执行环境会负责追踪代码执行过程中使用的内存,而非 C C++ 之类的语言需要开发者去手动释放内存。
故编写 JavaScript 脚本时,开发人员无需关注内存的使用,所需要的内存分配以及回收都实现了自动管理。
垃圾收集器会按照固定的时间间隔(或代码执行的预定时间间隔),找出不再继续使用的变量然后释放其占用的内存。
垃圾收集器运行时,会对无用的变量进行标记,备将来收回所占内存。而标记无用变量的策略因实现而异,具体到浏览器的实现主要有如下两种:

标记清除

最常用的垃圾收集方式。

1
2
3
4
5
6
7
来自MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management
把“对象是否不再需要”简化定义为“对象是否可以获得”。
假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

引用计数

跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型的值赋值给该变量,则这个值的引用次数为 1。 如果同一个值被赋值给另一个变量,引用次数加一。相反如果包含值引用的变量取得另一个值,那么引用次数减 1.当这个值的应用次数变成 0,则对其占用内存空间进行回收。

不足

循环引用,两个值的引用次数都是 2,不会被回收

1
2
3
4
5
6
function foo(){
var a = {}
var b = {}
a.bAttr = b;
b.aAttr = a;
}

写在结尾

基础!基础!基础!

显示 Gitment 评论