了解 JavaScript 的内存模型有助于了解 constlet,以及函数的参数传递。

内存模型图:

(图片来自 网络)

简化版就是:

(图片来自 MDN)

从图中可以看出,组成 JavaScript 内存的主要部分有:调用栈(Call Stack),堆(Heap),回调队列(Call Queue),Web API,以及事件循环。

思考如下代码:

1
var numberA = 2

当执行这段代码时,将会有以下操作:
1、为变量 numberA 创建唯一标识符(identifier)
2、在内存中分配一个地址(在运行时分配)
3、将值 2 存储在分配的地址

通俗地说是变量 numberA 保存了值 2,其实更准确地说是 numberA 保存的地址的值为 2

我们再创建一个变量,并把 numberA 赋值给它:

1
var numberB = numberA

由于 numberA 指向的是一个内存地址,所以 numberB 也指向了那个内存地址:

所以访问 numberB 获得的值也是 2

接下来对 numberA 进行以下操作:

1
numberA = numberA + 1

大家都知道 numberA 的值会变成 3,但是 numberB 并不会随着 numberA 的变化而变化,因为在 JavaScript 中,数字是基本类型值,而基本类型值是不可变的,也就是说该内存为值 2 的内存地址,会一直都是保存值 2

JavaScript 中的变量可能包含两种不同数据类型的值:基本类型指和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。

简单地说,基本数据类型包括:Undefined、Null、Boolean、Number 和 String(ES6 中新增了 Symbol)。引用类型就是 Object、Array、Function 等。

也就是说处理字符串与以上处理数字是一样的:

1
var stringA = 'abc'

对变量 stringA 进行赋值:

1
stringA = stringA + 'd'

对变量 stringA 进行重新赋值:

在调用栈中主要保存基本数据类型(也会有函数),堆中主要保存引用类型。

引用类型变量的声明和赋值

思考以下代码:

1
2
3
var objectA = {
a: 1
}

当运行上面的代码时,会发生的情况与上面创建 numberA 的情况类似,但是在 objectA 存储的地址的值将不是对象,而是在堆内存中该对象的地址:

接下来创建一个新变量 objectB 并把 objectA 赋值给 objectB

1
var objectB = objectA

此时 objectB 指向了和 objectA 同样的内存地址:

这个时候操作 objectB 所做出的修改也会反应到 objectA 中,因为现在 objectAobjectB 指向的是同一个对象:

1
2
objectB.a = 2
console.log(objectA.a) //2

函数的参数传递

在 JavaScript 中所有函数的参数都是按值传递。

思考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var numberA = 3
var objectA = {
a: 3
}

function foo(num, obj) {
num = 2
obj.a = 2
}

foo(numberA, objectA)

console.log(numberA) //3
console.log(objectA.a) //2

在进入 foo 函数时,会把 numberAobjectA 的内存地址复制给 foo 函数的局部变量 numobj:

执行 foo 函数的代码,修改相应的变量:

const 声明

const 声明的变量是常量,不能被改变其指向。

1
const arrA = [1]

可以对 arrA 指向的数组进行 pop, push, shift 等数组操作:

1
2
3
4
5
6
arrA.push(2)
arrA.push(3)
arrA.push(4)
arrA.pop()
arrA.shift()
console.log(arrA) // [2, 3]

但是不能对其进行重新赋值,也就是不能指向一个新的地址:

1
arrA = [] //  报错

一般来说,我们应该尽可能多地使用 const,只有当我们知道某个变量将发生改变时才使用 let

总结

  • 组成 JavaScript 内存的主要部分有:调用栈(Call Stack),堆(Heap),回调队列(Call Queue),Web API,以及事件循环
  • 基本类型的值是不变的,主要存在调用栈中
  • 引用类型的值是可变的,主要存在堆中
  • 函数的参数传递是值传递,传递的是变量所指向的地址
  • const 声明的变量不能被改变的意思是不能重新指向新的地址
  • 一般情况我们都尽可能使用 const 去声明变量,只有当我们确定知道某个变量会改变时使用 let

参考

JavaScript 是如何工作的:JavaScript 的内存模型

MDN:并发模型与事件循环

《JavaScript 高级程序设计(第三版)》