原始值与引用值
ECMAScript 变量可以包含两种不同类型的数据:原始值
和 引用值
-
原始值
- 是存储在
栈(stack)
中的简单数据段,它们的值直接存储在变量访问的位置。 - 包含 6 种:Undefined、Null、Boolean、Number、String 和 Symbol。
- 存储在栈中的原因:因为这类原始类型占据的
空间是固定的
,所以可将他们存储在较小的内存区域--栈
中。这样存储便于迅速查寻变量的值
。
- 是存储在
-
引用值
- 是存储在
堆(heap)
中的对象,也就是说,存储在变量处的值是一个指针,指向存储对象的内存处。 - 存储在堆中的原因:引用值的大小会
改变
(如对象的属性等),所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在栈空间中的值是该对象存储在堆中的地址
,而地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
- 是存储在
原始值 | 引用值 | |
---|---|---|
存储位置 | 栈(stack) | 堆(heap) |
存储内容 | 简单数据段 | 对象的地址 |
复制值
- JavaScript 不允许直接访问内存位置,也就是不允许直接访问保存在堆内存中的对象。
- 所以在访问一个对象时,实际上操作的是对该对象的
引用
(对象在堆内存中的地址
),然后再按照这个地址去获得这个对象中的值,这就是按引用访问
。 - 原始类型的值(栈内存中的值)是可以直接访问到的。
原始值的复制
在通过变量把一个原始值赋值到另一个变量时,原始值会被直接复制到新变量的位置。这两个变量可以独立使用,互不干扰。
let num1 = 5;
let num2 = num1;
复制代码
在这个例子中,a 和 b 这两个变量是完全独立的,只是拥有相同的值而已,因为 b 的值是 a 的值的副本。
引用值的复制
- 在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在
堆内存
中的对象。 - 操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。
let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // "Nicholas"
复制代码
在这个例子中,根据定义‘存储在变量中的值也会被复制到新变量所在的位置’,这里复制的值其实是 obj1 的指针,也就是对象的地址 ‘0x1234’,这样 obj1 和 obj2 两个变量都指向堆内存中的同一个对象。
传递参数
牢记: ESMAScript 中所有函数的参数都是
按值传递
的!!!
如何记忆:函数参数的按值传递,与复制值的机制相同。即,原始值直接复制该值,引用值复制对象的地址。
这里使用“红宝书”中经典的三个例子来说明究竟如何记忆函数的参数传递。
原始值作为函数的入参
example 1:
function addTen(num) { // num 是一个局部变量
num += 10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30
复制代码
在这个例子中:
- 函数 addTen() 有一个参数 num,它其实是一个
局部变量
,函数执行完之后就会销毁。 - 在调用时,函数的入参是 count,是一个
原始值
。 - 由于函数的参数是 “
按值传递
” 的,因此将原始值复制,传入函数。 - 接下来,遵从原始值的复制机制,将 count 的值复制一份,传入函数内部,赋值给
函数作用域内的变量 num
。 - 参数 num 和变量 count 互不干扰,它们只不过碰巧保存了一样的值。
ECMAScript 中函数的参数就是局部变量。
引用值作为函数的入参
example 2:
function setName(obj) {
obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
复制代码
在这个例子中:
- 在调用时,函数的入参是 person,是一个
引用值(对象)
。 - 因此将引用值复制,传入函数。
- 接下来,遵从引用值的复制机制,将 person 的值复制一份,传入函数内部,这时传入函数局部变量 obj 的值就是
person 的地址
。
example 3:
function setName(obj) {
obj.name = "Nicholas";
obj = new Object(); // 这两句要注意
obj.name = "Greg"; // 这两句要注意
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"
复制代码
在这个例子中:
- 在调用时,执行到 obj = new Object() 之前的所有步骤都没有变化,传入函数的参数是 person 对象在堆内存中的地址。
- 执行 obj = new Object() 之后,在
堆内存中重新开辟了一段内存
,将其地址
赋给了 obj,因此 obj 的值改变了,不再是 person 的地址值,改为指向新的内存地址。而 person 的值没有改变,故指向也没有改变。
总结
时刻牢记:
- 变量存储在
栈内存
中; - 对象存储在
堆内存
中; - 变量中存储的值的类型,一种是
原始值
,一种是对象的引用(对象的地址)
; - 函数的参数传递,遵从
按值传递
原则,可以记忆为传递的是栈内存
中的值,要么是一个原始值,要么是一个地址。
参考文献
- JavaScript 高级程序设计(第四版)P83-P86
- ECMAScript 原始值和引用值
- javascript传递参数如果是object的话,是按值传递还是按引用传递
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END