Life is too short to waste a second

JavaScript 深入系列 按值传递

前言

首先需要明确的是 JavaScript 只有按值传递,有文章中写道共享传递(Call By Sharing),但我认为它依然是按值传递的一种。

什么是按值传递呢?

也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。见末尾 Example 4。

JavaScript 中的内存分配

深入理解按值传递,我们需要先知道 JavaScript 变量的内存分配,严格来讲,所有的变量都是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。
JS内存分配图
简单的说,JS 的基础数据类型都是直接存值,而数组,对象这是存放堆内存的地址。

知道了内存分配,我们还需要知道:
运算符 = 就是创建或修改变量在内存中的指向
初始化变量时为创建,重新赋值即为修改

例子

Example 1

var a = 1; // a = 1
var c = a; // c = 1
a = 2; // 重新赋值a
console.log(c); // 1

执行完第二行代码后,内存中的分布

变量
a 1
c 1

执行 a=2 则修改 a 的值为 2,改变后的内存分布

变量
a 2
c 1

再来看 c,并没有应为 a 的改变而改变,因为 a 的值直接修改为 2 了

Example 2

var a = { b: 1 }; // a = {b: 1}
var c = a; // c = {b: 1}
a.b = 2; // 重新赋值对象a中的属性b
console.log(c); // {b: 2},// c也随着修改,从

执行完第二行代码后,内存中的分布

变量
a 堆地址 { b: 1 }
c 堆地址 { b: 1 }

执行 a.b=2 则修改 a.b 的值为 2,改变后的内存分布

变量
a 堆地址 { b: 2 }
c 堆地址 { b: 2 }

因为 a 是堆地址,访问 a.b 就会修改堆中 b 的值,c 也是这个对象的地址,所以输出 c 也改变了,看上去是引用(c 获取 a 的引用),实际还是按值(拷贝 a 的地址)。看下面的 example 3 就可以知道 c 并不是 a 的引用

Example 3

var a = { b: 1 }; // a = {b: 1}
var c = a; // c = {b: 1}
a = 2; // 重新赋值对象a中为2
console.log(c); // {b: 2}

执行完第二行代码后,内存中的分布

变量
a 堆地址 { b: 1 }
c 堆地址 { b: 1 }

执行 a=2 则修改 a 的值为 2,改变后的内存分布

变量
a 2
c 堆地址 { b: 1 }

赋值 a 为 2,直接修改 a 的值为 2,堆并没有受到影响,所以 c 还是{ b: 1 }

Example 4

var value = 1;
function foo(value) {
  value = 2;
  console.log(value); //2
}
foo(value);
console.log(value); // 1

按值传递,可以理解为下方代码

var value = 1;
function foo(value) {
  var _value = value; // value是传入的实参,函数中新建形参变量并复制实参的值
  _value = 2;
  console.log(_value); //2
}
foo(value);
console.log(value); // 1

总结

虽然例子中没有举例函数的更多情况,但是理解了 JS 的内存分布和按值传递,其他函数也就很好分析了。结论就是 JavaScript 始终是按值传递。

参考资料
https://github.com/mqyqingfeng/Blog/issues/10
https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/yi-3001-nei-cun-kong-jian-xiang-jie.html

发表评论

电子邮件地址不会被公开。