在各种教程中,大家可以经常看到这句话:
ECMAScript中所有参数传递的都是值,不可能通过引用传递参数
但是在实际应用的时候,似乎没有这么简单,今天我们来聊一下在js调用函数传递变量参数时,到底是值传递还是引用传递呢?在此之前,先和大家插入一个小知识:
关于引用变量赋值问题
- 两个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到是修改之后的数据
var obj1={name:'Tom'}
var obj2=obj1
obj2.age=12
console.log(obj1.age) //12
function fn(obj){
obj.name='A'
}
fn(obj1)
console.log(obj2.name) //A
在真正明白这个例子前,我们先来确定一下自己是否真正理解变量的含义:var a=xxxx,a内存中到底保存的是什么?
我们分三种情况来讨论:
1.xxxx是基本数据,保存的就是这个数据
var a=3;
2.xxxx是对象,保存的是对象的地址值
var a=function(){ }
3.xxxx是一个变量,保存的xxxx的内存内容(可能是基本数据,也可能是地址值)
重新回到我们的这个例子,obj2和obj1指向了同一个对象,并且通过obj2对该对象的age属性进行了修改,这个时候改变的是obj2指向的对象的本身,又因为obj1和obj2指向的是同一个对象,所以在打印obj1.age的时候,输出的结果是更改过后的结果,也就是12。
在通过fn函数将obj1传递进去之后,相当于是obj=obj1,此时已经将obj和obj1指向了同一个对象,这个时候,obj、obj1、obj2三个变量指向了同一个对象。那么在函数中对对象的name属性更改了之后,再打印obj2的name属性就理所当然的为更改之后的值,即A。
- 两个引用变量指向同一个变量,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象
var a={age:12}
var b=a
a={name:'BOB',age:13}
b.age=14
console.log(b.age,a.name,a.age)//14 Bob 13
function fn2(obj){
obj={age:15}
}
fn2(a)
console.log(a.age) //13
在这个例子中,var b=a 让b和a指向了同一个对象,但是,a={name:‘BOB’,age:13}使a的指向对象发生了改变,但是b还指向之前的对象,所以在b的age属性发生改变的时候,与a指向对象已经没有关系了,所以输出b的age属性应该是更改之后的age属性值,但是a的name属性和a的age属性应该是新指向对象的值。
在通过fn2函数将a传进去之后,相当于obj=a,此时obj和a指向了同一个对象,但是在函数体内,obj={age:15} 使obj的指向对象发生了改变,此时obj已经成了一个局部的变量,和a指向的对象已经互不相干,在执行完函数之后obj对象将自动销毁,并且obj所做的任何操作与全局变量的a没有任何关系,所有打印出的a的age属性仍然为13。
好了,在有了上述的基础之后,我们开始探讨我们今天的主要问题。
值传递还是引用传递?
我们分为两个部分来分析:
- 基础数据类型
var a = 10
function add(num){
num+=10
}
add(a)
console.log(a) //10
基本数据类型比较简单,在add函数中传入a的时候,只是将a的值复制了一份给了num,num可以看做一个局部的变量,他所做的任何操作都影响不到全局变量a,函数运行结束的时候num也会随之销毁,所以在打印a的时候,a的值仍然为初始值10。在这里值传递的思想完美诠释,没有任何问题。
- 对象类型
var student = new Object()
function addNum (obj) {
obj.num = '23'
}
addNum(student)
console.log(student.num) //23
在对象类型的时候,我们发现和基本数据类型不同,函数内部对obj的num属性的改变竟然也改变了student对象的num类型,这个时候,这个结果和我们开篇的那句话ECMAScript中所有参数传递的都是值,不可能通过引用传递参数似乎矛盾了???这是怎么回事呢?
把student对象实例作为参数传递给obj时,是将student所指对象的地址复制了一份给obj,这样obj空间所存的地址和student就都指向了共同的存储空间。也就是说,obj和student指向了同一个对象。等等!!!这不就回到了我们上一个问题中的引用变量赋值问题吗?有没有豁然开朗的感觉?!因obj和student指向了同一个对象,那么,在obj对对象更新的时候,studen同样也更新,打印出的student的num值理所应当的就为更改之后的值了,即23。
- 练习一下
var student= new Object()
function addName(obj){
obj.name = ‘haha’
obj = new Object()
obj.name = ‘lala’
}
addName(student)
console.log(student.name) //haha
在这个例子中,将student传入addName函数中,此时student和obj指向了同一个对象,并且通过 obj.name = ‘haha’对对象添加了name属性并且赋值为haha。但是在下一步,obj = new Object(),也就意味着obj重新指向了一个新的对象,此时obj变为了一个局部的变量,并且与全局变量student毫无关系,在函数执行完毕的时候,obj也会随之销毁。所以在打印student的name属性的时候会打印出来haha。