Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 是传值调用还是传引用调用? #32

Open
nodejh opened this issue Feb 19, 2017 · 11 comments
Open

JavaScript 是传值调用还是传引用调用? #32

nodejh opened this issue Feb 19, 2017 · 11 comments

Comments

@nodejh
Copy link
Owner

nodejh commented Feb 19, 2017

1. 例子

先来看两个个来自于 《JavaScript 高级程序设计》P70-P71 的两个例子。

1.1. 基本类型参数传递

function addTen(num) {
  num += 10;
  return num;
}

var count = 20;
var result = addTen(count);
alert(count); // 20, 没有变化
alert(result); // 30

书上解释说,JavaScript 参数传递都是按值传参。

所以传递给 addTen 函数的值是 20 这个值,所以函数执行结束原始变量 count 并不会改变。

1.2. 引用类型参数传递

function setName(obj) {
  obj.name = 'Nicholas';
  obj = new Object();
  obj.name = 'Greg';
}

var person = new Object();
setName(person);
alert(person.name); // Nicholas

为什么结果是 Nicholas 呢?

变量存储方式

疑问:如果是传值,那应该是把 person 变量的值(也就是一个指向堆内存中对象的指针)传递到函数中,obj.name = 'Greg'; 改变了堆内存中对象的属性,为什么 person.name 还是 Nicholas

2. 传值还是传引用?

让我们再将上面两个例子综合为下面的例子:

function changeStuff(a, b, c) {
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);    
console.log(obj2.item);

最终的输出结果是:

10
changed
unchanged

所以 JS 到底是传值调用还是传引用调用呢?要弄清楚这个问题,首先我们要明白到底什么是传值调用(Call-by-value)和传引用调用(Call-by-reference)

2.1. 传值调用(Pass by value)

在传值调用中,传递给函数参数是函数被调用时所传实参的拷贝。在传值调用中实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)。

changeStuff 的参数 a b cnum1 obj1 obj2 的拷贝。所以无论 a b c 怎么变化,num1 obj1 obj2 都保持不变。

问题就在于 obj1 变了。

2.2. 传引用调用(Pass by reference)

在传引用调用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。

也就是说 changeStuff 函数内的 a b c 都分别与 num obj1 obj2 指向同一块内存,但不是其拷贝。函数内对 a b c 所做的任何修改,都将反映到 num obj1 obj2 上 。

问题就在于 numobj2 没变。

从上面的代码可以看出,JavaScript 中函数参数的传递方式既不是传值,也不是传引用。主要问题出在 JS 的引用类型上面。

JS 引用类型变量的值是一个指针,指向堆内存中的实际对象。

2.3. 传共享调用(Call by sharing)

还有一种求值策略叫做传共享调用(Call-by-sharing/Call by object/Call by object-sharing)

传共享调用和传引用调用的不同之处是,该求值策略传递给函数的参数是对象的引用的拷贝,即对象变量指针的拷贝。

也就是说, a b c 三个变量的值是 num obj1 obj2 的指针的拷贝。 a b c 的值分别与 num obj1 obj2 的值指向同一个对象。函数内部可以对 a b c 进行修改可重新赋值。

function changeStuff(a, b, c) {
  a = a * 10; // 对 a 赋值,修改 a 的指向,新的值是 a * 10
  b.item = "changed"; // 因为 b 与 obj1 指向同一个对象,所以这里会修改原始对象 obj1.item 的内容
  c = {item: "changed"}; // 对 c 重新赋值,修改 c 的指向,其指向的对象内容是 {item: "changed"}
}

3 代码分析

接下来让我们再来分析一下代码。

3.1 变量初始化

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

变量初始化

3.2 调用函数

changeStuff(num, obj1, obj2);

调用函数

可以看到,变量 a 的值就是 num 值的拷贝,变量 b c 分别是 obj1 obj2 的指针的拷贝。

函数的参数其实就是函数作用域内部的变量,函数执行完之后就会销毁。

3.3 执行函数体

a = a * 10;
b.item = "changed";
c = {item: "changed"};

执行函数体

如图所示,变量 a 的值的改变,并不会影响变量 num

b 因为和 obj1 是指向同一个对象,所以使用 b.item = "changed"; 修改对象的值,会造成 obj1 的值也随之改变。

由于是对 c 重新赋值了,所以修改 c 的对象的值,并不会影响到 obj2

4. 结论

从上面的例子可以看出,对于 JS 来说:

  • 基本类型是传值调用
  • 引用类型传共享调用

传值调用本质上传递的是变量的值的拷贝。

传共享调用本质上是传递对象的指针的拷贝,其指针也是变量的值。所以传共享调用也可以说是传值调用。

所以《JavaScript 高级程序设计》说 JavaScript 参数传递都是按值传参 也是有道理的。


参考

@ClarenceC
Copy link

写得这么详细,赞一个

@u1in
Copy link

u1in commented Nov 23, 2017

简单易懂!赞!

@Dream4ever
Copy link

传值调用(Call-ny-value)=> 中间那个单词 by 错了,嘿嘿。

@chenyin151
Copy link

最后一句话突然点醒梦中人,引用传值其实是copy了对象中的指针,指针其实也是值,所以引用传值是一种copy对象指针的传值调用,可以这样理解吗?

@nodejh
Copy link
Owner Author

nodejh commented May 30, 2019

@chenyin151 是的。对于上文中 changeStuff 函数中的 b,对 b 指向的内存进行操作,就会导致原来的值被改变。而对于 c,因为这份 copy 的指针重新赋值了,所以 c 和 obj2 就没有关系了,所以对 c 指向的内存进行操作,就不会影响其他数据。

@duola8789
Copy link

学习了,谢谢

@xingchengyusi
Copy link

谢谢,好久没看有点忘了。。。。

最后传的还是值,只不过这个值有可能是引用。

@plh97
Copy link

plh97 commented Dec 10, 2019

这就是一个 bug. 神坑

@caisj-dev
Copy link

参数可以等价于函数block 内定义的局部let变量,就很好理解了。这个let 变量指向了实参对象,当然可以也可指向其他对象啦。
看JavaScript 高级程序设计里的function 那一章节的argument 相关内容就很好理解。

@shaochila
Copy link

太强了

@fongfai
Copy link

fongfai commented Aug 14, 2022

这个问题可以通过Dan的心智模型完美解决https://justjavascript.com/learn/08-properties

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests