js_关于为什么'函数的参数都是按值传递'的理解
在向函数传递参数的过程中,参数的传递实际上是一种复制变量的操作.变量因为分为基本类型值和引用类型值,故在传递参数这一过程中有不同的表现.但终究它们都是按值传递的.
基本类型值与引用类型值的复制
- 栈内存(stack)用于保存基本类型数据和引用类型数据的指针.它的读取方式是先进后出(LIFO last-in-first-out)
- 堆内存(heap)用于保存引用数据类型,比如object,array.heap没有结构,数据任意存放.
- 基本类型值在复制过程中,被赋值变量获取的是来源的副本.
- 引用类型值在复制过程中,复制的是引用类型值在栈内存中的指针的副本,堆内存的数据没有任何变化.
var a = 1;
var b = a;
b = 2;
console.log(`a:${a}`);//a:1
console.log(`b:${b}`);//b:2
如上代码所示,在将a赋值给b的过程里,实际上是将a的副本赋值给b,在赋值完成后无论对基本类型值b做任何修改都不会干扰到a的值.
function setName(obj) {
obj.name = 'Nicholas';
obj = new Object();
obj.name = 'Greg';
}
var person = new Object();
setName(person);
console.log(person.name); //'Nicholas'
如上代码所示,setName函数接收一个对象的指针的副本作为参数.将person对象的指针的副本作为参数传递到setName函数中.(赋值给arguments对象的obj元素)
执行代码 obj.name='Nicholas' 时,obj作为arguments对象中的元素,其值是指向堆内存中的对象实体的指针.因此对其添加元素将会根据指针指向直接修改堆内存中的对象.(注意obj只是保存的person对象的指针的副本,全局变量perosn依然保存着指针)
执行代码 obj=new Object() 时,前面说过obj是arguments对象的元素,此时在局部作用域中新建了一个对象实体,并将它的指针赋值给obj元素,此时obj元素与堆内存中的person对象彻底脱钩,不再有任何联系.
执行代码 obj.name='Greg' 时,因为obj已经与原本传入的person对象的指针副本无任何关系,现在保存的是局部作用域下的一个新对象的指针,为其添加name属性,直接修改的是堆内存中的新对象.
执行代码完毕,销毁函数的局部作用域以及该作用域下的变量对象,arguments对象中的obj元素保存的是堆内存中一个新对象的引用.obj元素随着局部作用域的销毁而销毁.失去了引用的引用数据也同样在堆内存中被销毁.
总结
- 简单数据类型值就像是小纸片,上面写好了数据并交由一个人(变量)保管.当其它人(变量)需要这个数据时,他需要复制这个数据,也就是用另一张小纸片抄一份.虽然数据相同,但是小纸片彼此之间并无关联.其它人(变量)的小纸片上的数据可以被随时被擦掉更改.而且小纸片的保存方式是随身携带.(栈内存储存)
- 引用数据类型就像是一所房子的钥匙.房子本身是建立在土地上的(堆内存储存),一个人(变量)是无法随身携带房子的,但是他可以随身携带钥匙(栈内存储存).在这所房子创建时它就会有一个主人,这个主人保存有钥匙(创建对象的赋值操作),当执行引用类型数据的复制时,相当于持有房子的主人(变量)新配了一把钥匙给另一个人(变量),这样两个人都可以对房子内的东西进行添加修改删除.但是房子终究只有一个房子,只是多了一个人可以对它进行操作而已.
- 函数的传参过程就是数据的复制过程,操作的都是栈内存中的数据,堆内存中保存的数据并没有被操作,也因此说"函数的参数都是按值传递".