JavaScript学习(一) —— 内存机制

作为前端开发,应该很少有人关注与内存有关的部分,后端应该比较多。搞清楚内存机制对学习有关闭包、作用域(链)、按值和引用传递、深拷贝对象、实例化一个对象时js都做了什么等知识时,会有很大的帮助。

一、内存空间中的栈(stack)和堆(heap)

JavaScript的数据类型分基本数据类型引用数据类型(以ES5为准),基本数据类型包括Number、String、Boolean、Null、Undefined。引用数据类型是一种复杂的数据类型Object,它是一组无序的键值对的集合,引用类型包括数组Array和函数Function。

当声明一个变量时,如果声明的是基本数据类型就存储在内存的栈(stack)中,如果是引用数据类型则存储在堆(heap)中。

这里要说一下数据结构数据结构是计算机中存储和组织数据的方式。现在常用的数据结构有数组(Array)、树( Tree)、链表( Linked List)、栈( Stack)、队列(Queue)、堆(Heap)等等。JS中基本数据类型使用的是栈数据结构。

1. 栈数据结构

举个例子:

var str = 'some string';
var num = 10;
var isTrue = true;

上面定义了三个基本数据类型变量str、num、isTrue,它们在内存中的存取方式遵循先入后出,后入先出(LIFO)的方式。见上图右半部分画的栈容器(蓝色边框),底部封口,顶部不封口,把这个容器比做栈的话。
以这个例子为准:

  • str是第一个入栈的数据,它的值是’some string’,为了方便解释,给他一个序号是1 (图中蓝色方框序号标记)。
  • num第二个入栈,它的值是10,序号是2。
  • 第三个入栈的是isTrue,值是true,序号是3。
  • 序号3的数据在最顶部,序号2的数据在中间,最底部的是序号1的数据。
  • 如果要从栈中取数据,最先取到的是最顶部的序号3的数据。
  • 如果想取出序号1的数据,需要先让序号3的数据出栈,然后让序号2数据出栈,最后才能取到序号1的数据。
2. 堆数据结构

定义一个引用类型的变量,在变量对象中存放一个内存地址,这个内存地址大概是0x0012ffc8这样,它是一个16进制码,JavaScript并不能打印出一个具体对象的内存地址。在变量对象中一个变量对应的内存地址引用了堆内存中一个对象。JavaScript不能直接访问堆内存中具体位置,它操作的是对象的引用而不是实际的对象。
定义两个引用类型的变量:

var obj1 = { a: 1 };
var obj2 = { b: 2 };

图解堆内存空间是这样的:

  • 变量对象中obj1对应的值是内存地址1它引用堆内存空间中的对象{ a: 1 },同样obj2对应的值是内存地址2,它引用堆内存空间中的对象{ b: 2 }
  • 我们所认为的通过变量对象中的变量名找到对应的具体对象的过程,中间还有一个我们感知不到的过程:通过变量对象中的变量名对应的内存地址,找到堆内存空间中的实际对象。
二、 按值和按引用传递

复制一个变量时,如果复制的是基本类型的数据,是按值传递。如果复制的是引用类型的数据,是按引用传递。

1. 按值传递
// 复制基本类型的变量, 按值传递
var num1 = 10;
var num2 = num1;
num2 = 20;
console.log(num2) // 20
console.log(num1) // 10 
// 复制num1给num2时是按值传递,更改num2的值没有影响到num1的值

按值传递用栈数据结构如上图:

  • 执行复制变量 var num2 = num1时,num2的值也是10,但是在栈内存中创建了一个新数据(num2)入栈,它的值是10。
  • 执行修改num2的值为20时,修改的是栈内存中新创建的数据(num2)的值,它不影响num1的值。num1还是10,num2为20。
2. 按引用传递
// 复制引用类型的变量, 按引用传递
var obj1 = { a: 1};
var obj2 = obj1;
obj2.a = 2;
console.log(obj2.a) // 2
console.log(obj1.a) // 2,
// obj1.a也变成2,因为obj1和obj2的内存地址引用了堆内存空间中的同一个对象
  • 在执行var obj2 = obj1复制时,在变量对象中,obj2存的内存地址和obj1相同,指向堆内存中同一个对象。
  • 修改obj2.a的值,同时也修改了obj1.a的值。

一个数组的例子:

var arr = [ 'a', 'b', 'c' ];
var arr1 = arr; // 复制数组给变量arr1
var str = arr[0]; // 复制数组中第0个元素给变量str,第0个元素本身是基本类型变量
console.log(arr1); // ["a", "b", "c"]
console.log(str);	 // "a"
arr1[2] = "d"; // 按引用传递
str = "e"; // 按值传递
console.log(arr[2]); // "d"
console.log(arr[0]); // "a"
  • 复制数组arr给变量arr1时是按引用传递,复制数组中第0个元素给变量str时,第0个元素本身是字符串类型,属于基本类型变量,是按值传递。
  • arr1和arr指向同一个堆内存空间的对象,所以修改arr1的第3(下标是2)个元素的值时,影响了arr的元素跟着改变。
  • str是按值传递,修改str的值不会影响到arr[0]的值。
三、 垃圾回收机制

对比C/C++语言需要手写代码实现垃圾回收,JavaScript的垃圾回收机制是自动进行的,写代码的人感知不到这个过程。JS的作用域分全局作用域和函数作用域。
全局中用var定义的变量同时挂载到了window对象上,在函数体中用var定义的变量属于这个函数级的作用域。

var global_var = 'global';
function fn() {
  var private_var = 'private';
  console.log(private_var);
  console.log(global_var);
}
fn();

以上面代码为例,调用一个函数的过程:

  1. 创建这个函数fn的执行上下文(Execution Context)
  2. fn的执行上下文加入到函数调用栈(Call Stack)
  3. fn的执行上下文出栈
  4. 函数fn执行结束。

在把fn的执行上下文入栈到出栈的过程中,在内存中给函数级作用域变量private_var分配了空间,并在执行函数完毕时回收了该变量的内存。

最后,本文涉及到的:

  • 变量对象(Variable Object)
  • 执行上下文(Execution Context)
  • 函数调用栈(Call Stack)

等概念会在后面的文章进行讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值