JavaScript并非“按值传递”
置顶文章:《纯CSS打造银色MacBook Air(完整版)》
上一篇:《拥Bootstrap入怀——模态框(modal)篇》
作者主页:myvin
博主QQ:851399101(点击QQ和博主发起临时会话)
声明
本文是笔者在查阅资料的基础上根据自己的理解来写的,但对于一些问题博友可能会有争议,欢迎提出异议,欢迎讨论。
关于笔者的观点总结,请务必看文章最后一段的3点总结。
在评论区,博友烧点饭对按值传递用《高级程序设计》给出了补充,笔者误会了博友的好意,特在此向博友烧点饭表示歉意。点击博友ID烧点饭可以进去其主页查看博友空间。详情请查看评论区。
同时,也非常感谢各位答主的交流,笔者确实有所受益。希望我们都共同进步。
写在前面
首先,在此声明,这不是标题党。
其次,再次声明,文中会有部分引用的文章,笔者会在文中指出,而且,文中也会有部分笔者自己的理解,阅读后如有意见建议,欢迎在评论区指出。
一、提出问题
也许会有人质疑:
“在JavaScript中,基本类型是按值传递,引用类型也是按值传递,你却告诉我JavaScript中并不是按值传递?”
的确,在JavaScript中,基本类型是按值传递,但是“准确”来说,引用类型并不是按值传递,当然也不是按引用传递,那到底是什么传递?
先给个引子。
在《JavaScript高级程序设计(第三版)》中关于参数传递是这样讲的,笔者引用如下:
ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。有不少开发人员在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
书中明确讲到参数的传递是按值传递的,笔者却硬要说引用类型不是按值传递的,请相信,笔者没有瞎。
笔者在反复看这一块的时候思想活动是这样的:不懂————略懂————不懂————认为引用类型是按引用传递————还是不懂————头要炸了。
可以看到,笔者在这里貌似犯了一个一部分人会犯的理解:引用类型的传递虽然可以看做是引用作为值的复制和传递,但是引用类型的传递有一些按引用传递的影子在里面,但是书中点出是按值传递。
到这里,问题出来了:引用类型值的传递到底是不是按值传递,或者说,如果是按值传递,那到底应该怎么理解?如果不是按值传递,那该是按嘛传递?(笔者并不认为它是按值传递)
不知道大家当时是怎么理解这个问题的,或者现在是怎么理解这个问题的。反正当时笔者对于书中的解释并不是很清楚。
上网随便一搜,关于JavaScript的按值传递的回答解释倒是不少,但好多都是Ctrl+C,Ctrl+V来的,有的关于同一个问题不同的文章解释的却不相同,然后一路查,查到了维基,查到了stackoverflow,终于有了眉目。
现在这里给出参考相应的维基百科和stackoverflow地址,可提前预览,关于维基百科请查看英文,中文的内容会大幅缩减。
二、分析问题
关于引用类型值传递的错误理解
(1)、基本类型值的传递
首先,简单说一下基本类型值的传递,对于基本类型值的传递理解的可以直接跳过了。
首先给出结论:基本类型值的传递是按值传递的。
先来个简单的例子:
function func(num){
num=10;
return num;
}
var outer=20;
var result=func(outer);
console.log(result,outer);
打印结果分别是:10和20。
在这段代码中,实参outer作为参数传递给函数func,所以该实参被复制给形参num,这两个参数是完全独立的,各回各家各找各妈。在函数中,num值被赋值10,但是并不影响函数外部的outer,outer依然是坚挺的20。
这个结论很好理解,基本类型值的大小是固定不变的,是放在栈中的,我们复制一个基本类型值,或者给一个函数的形参传递一个基本类型值,就相当于新开辟了一块内存空间来存放这个基本类型值的副本,这两个值完全相等,相互操作互不干扰。
(2)、引用类型值的传递
对于引用类型的传递,笔者先给出一个笔者当时的错误理解:引用类型值的传递是按引用传递的。请注意:这里是一个错误理解,只是为了更好分析这个错误在哪。
Eg1:先给出一个例子:
function setSex(obj){
obj.sex='girl';
}
var person = new Object();
setSex(person);
console.log(person.sex);
打印结果是:girl。
给出这样的错误理解:obj和person指向同一个对象,即两者保存的是相同的引用,修改obj就相当于修改person,所以当person作为引用类型参数传递给函数后,修改形参obj的sex属性,那person的sex属性自然是girl,所以你看,这不就是按引用传递吗。
Eg2:再给出一个例子:
function func(o2){
o2={name:'xiaoming'};
return o2;
}
var o1={color:'red'};
var result = func(o1);
console.log(o1,result);
这也是一个引用类型值的传递,如果引用类型值的传递是按引用传递的话,即o1和o2都指向堆中的同一个对象,那么返回的o2应该会和o1相同,但是打印结果是这样子的:
Object {color: "red"} Object {name: "xiaoming"}
即o1并没有因为随着o2的改变而改变。
当然,进行到这里并没有解决笔者在文章开头提出的问题。因为第二个例子仅仅是提出了一个反例而已,然并卵,并没有真正地解释“既然你口口声声大言不惭地说这不是按引用传递,那这到底是什么传递呢”。
下面,切入正题。
三、解决问题
注:以下部分内容引用自维基百科的Evaluation strategy,感兴趣的可以先点击链接查看尝鲜。
维基百科的这篇Evaluation strategy中介绍了多种传递策略,在这里笔者主要说一下和该博文相关的三种传递方式,即call by value、call by reference和call by sharing,即按值传递、按引用传递和按共享传递。
1、按值传递(call by value)
关于按值传递比较简单,在这里就不再赘述,简单引用文中的一段解释:
Call-by-value evaluation is the most common evaluation strategy, used in languages as different as C and Scheme. In call-by-value, the argument expression is evaluated, and the resulting value is bound to the corresponding variable in the function (frequently by copying the value into a new memory region). If the function or procedure is able to assign values to its parameters, only its local copy is assigned — that is, anything passed into a function call is unchanged in the caller's scope when the function returns.
上段英文理解起来也很简单,可自行阅读或pass。
2、按引用传递(call by reference)
按引用传递也叫pass-by-reference,它接收的是隐式引用,不是参数的副本,形参和实参指向的是同一个对象,所以修改形参,相对应的实参也会被修改,部分原文如下:
receives an implicit reference to a variable used as argument, rather than a copy of its value
对应着看上面刚提到的Eg2,JavaScript的引用类型值的传递并不是按引用传递,形参变化并没有导致实参做相应的变化。
那么接下来看最后一个相关的传递方式,也就是笔者比较认同的JavaScript引用类型值传参方式:call by sharing,按共享传参。
3、按共享传递(call by sharing)
注:这里是重点!!!
先来一段引用:
call by object-sharing" is an evaluation strategy first named by Barbara Liskov et al. for the language CLU in 1974.
按共享传递,也叫call by object或call by object-sharing,是麻省理工的Barbara Liskov在1974年在CLU语言中提出的,关于CLU编程语言的其他信息,可以点击这里跳转到百度百科查看。
使用按共享传递,函数接收的参数是对象引用的副本,它和按引用传参有一定区别:
按引用传递,改变形参,实参也会相应变化;按共享传递,修改形参的属性时,实参会相应改变,但是如果给形参重新赋值,即给形参重新分配一个引用,是不会影响外部的实参的。
在这里举两个例子:
(a)第一个例子就是上面提到的Eg2:
function func(o2){
o2={name:'xiaoming'};
return o2;
}
var o1={color:'red'};
var result = func(o1);
console.log(o1,result);
打印结果:
Object {color: "red"} Object {name: "xiaoming"}
在这里传递引用类型参数是按共享传参,按照上面提到的,按共享传递,修改形参的属性时,实参会相应改变,但是如果给形参重新赋值,即给形参重新分配一个引用,是不会影响外部的实参的,所以在该例子中,外部的实参o1并没有因形参o1的重新赋值而变化。
(b)第二个例子和第一个例子类似,只是重新new了一个:
function func(o2){
o2.color='blue';
o2=new Object();
o2.color='white';
return o2.color;
}
var o1={color:'red'};
var result = func(o1);
console.log(o1.color,result);
修改形参o2的color属性值为blue,按照按引用传递的特点,外部的o1的color属性值相应改变,接下来的o2=new Object()
虽然和形参o2同名,但是指向的已经不是同一个对象,即它们不是同一个引用(既然是new,自然是新的了,哈哈哈哈),所以再修改o2的color属性值外部实参也不会有任何变化,在函数调用完毕以后,这个new起来的o2就会被销毁。
所以,在这个例子中,打印结果是blue和white。
然后,在这里再点两点不太相关的东西,希望有助于我们理解:
首先是这一段引用:
It (call by sharing) is used by languages such as Python, Iota, Java (for object references), Ruby, JavaScript, Scheme, OCaml, AppleScript, and many others.
文中提到,按共享传递被应用到了许多语言中,其中也包括JavaScript,这样我们也在JavaScript中找到了关于按共享引用的影子,算是对上述的一个不太合适的小小的印证吧。
接下来是这一段引用:
However, the term "call by sharing" is not in common use; the terminology is inconsistent across different sources. For example, in the Java community, they say that Java is pass-by-value, whereas in the Ruby community, they say that Ruby is pass-by-reference, even though the two languages exhibit the same semantics.
文中提到,按共享引用其实并不是普遍被提到,在不同的语言中,它的别称都不太一样,比如Java中,称Java按值传递,在Ruby中,称Ruby按引用传递,其实它们表达的是相同的含义。
所以,在JavaScript中,笔者更倾向于把JavaScript中关于引用类型传递的所谓的“按值传递”当做是按共享传递的一个别称,但是它有别与JavaScript中基本类型值的按值传递。
总结问题
在此总结三点:
1. 关于书中的对于引用类型值的按值传递,可以理解为传递的是引用的副本,而对象的引用可以看做是一个值(它就是一个值),但是笔者不倾向于就因为这一点就把它认为是按值传递。
2. 关于JavaScript中的引用类型值的传递方式笔者更倾向于是按共享传递。这样的话,它将按值传递和按引用传递很好的却别开来,因为JavaScript中的引用类型值的传递有一部分按引用传递的特点在里面,比较容易混淆。
3. 笔者对于书中的按值传递的说法不否定,只是更将倾向于将引用类型值的按值传递理解为按共享传递的一种别称。
如果文中的内容有任何错误不足,恳请指出,同时也欢迎在评论区谈论。
Also known as "call by object" or "***********
转载请记得说明作者和出处哦-.-
作者:myvin
原文出处:http://www.cnblogs.com/myvin/p/4794680.html