js的函数传参之值传递

268 阅读3分钟

前言

之前写博客,经常需要引用一些基础的内容,每次都花不少时间找合适的文章,索性花点时间自己写,也当是巩固下基础。于是有了这个系列(JS核心基础)的文章。

目前已经完成的文章:

js从编译到执行过程 - 掘金 (juejin.cn)

从异步到promise - 掘金 (juejin.cn)

从promise到await - 掘金 (juejin.cn)

浅谈异步编程中错误的捕获 - 掘金 (juejin.cn)

作用域和作用域链 - 掘金 (juejin.cn)

原型链和原型对象 - 掘金 (juejin.cn)

this的指向原理浅谈 - 掘金 (juejin.cn)

概论

在《JavaScript高级程序设计》中,参数传递一节直接了当地说:

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

那么参数传递都有哪些形式,js的参数传递又有怎样的特殊之处?

本文将从内存的角度讲解这个问题。

一,值传递和引用传递的区别

1.1,值传递

值传递就是把函数外部的值复制给函数内部的参数,函数中对该值的修改不会影响原有值。

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

执行change时对应的内存图如下:

当执行change的时候,是把value=1的值作为实参传递进去,这样在change的执行上下文中的val=1,当执行到val = 2;的时候,change执行上下文中的val变量变成2。

1,值传递.drawio.png 可以看到全局上下文的value还是等于1,并没有被修改。这就是值传递。

1.2,引用传递

引用传递就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。

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

执行到foo函数中的时候,内存的状态如下图:

2,引用传递.drawio.png

看起来js传递引用类型的实参的时候,就是利用的引用传递。但是红宝书又明确说是按值传递。那问题出在那里了?

1.3,共享传递

引用传递的话,从理论上讲,如果函数中修改了该入参,原对象必被变更。因为修改的是引用值指向的堆内容,而不是修改引用地址(上图的1002),那么看如下代码:

var obj = {
    value: 1
};
function change(o) {
    o = 2;
    console.log(o); //2
}
change(obj);
console.log(obj) // { value: 1 }

对应的内存图解:

3,共享传递.drawio.png

可以看到。函数执行后,并没有修改外层的对象值,而是把引用地址修改了(o=2)。

其实这种传递叫共享传递:在传递对象的时候,传递对象的引用的副本。

也就是说: 按引用传递是传递对象的引用,而按共享传递是传递对象的引用的副本!

换句话说就是change上下文中的o和全局上下文的obj的值,是不相关的。o要改成啥是它自己的,只是最开始传入的时候,它是个引用值,能够访问和修改外层的obj罢了。

二,总结

所以说,红宝书中说的其实也没错:

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

对于普通类型的数据,它将复制一份。

对于引用类型的数据,他将引用值复制一份。

本质上,就是值的复制。