一直认为在JavaScirpt中,数据只有两种访问方式:基本类型数据是按值访问的,而引用类型的值是按引用访问的。
让我第一次对这种分法产生疑惑的是红宝书上说:传递参数时,不论是什么类型的值,它们都是按值传递的。
按值传递(call by value)
按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。
按引用传递(call by reference)
按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。 这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。
书上举了个例子:
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //Nicholas
第一次看到这章的时候,我看的云里雾里的,也查了不少资料,最后在旁边写了两行注释:
在不为person赋值时,函数内外地址相同,所以操作可以反应在外部;反之,地址不同,则内外联系就断了。
但是总感觉说的还不到位,而且事实上我自己也并不理解这句话的具体意思。但是我当时学的不多(虽然现在依然是菜鸟), 也不知道网上什么是对的什么是错的,也就放着了。
今天我又突然想到了这个问题,也搜了不少资料,知道了共享传递这个名词:(接下来是一段引用)
准确的说,JS中的基本类型按值传递,对象类型按共享传递的(call by sharing,也叫按对象传递、按对象共享传递)。 最早由Barbara Liskov. 在1974年的GLU语言中提出。该求值策略被用于Python、Java、Ruby、JS等多种语言。
该策略的重点是:调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用)。 它和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值。
接下来是我的理解
其实对象A在被赋值到变量B的时候,复制的是一份指向在堆内存的对象Object的指针给B,现在对象A和B都指向对象Object, 如果我单纯的给A或者B加上一个属性,比如A.name = "Nicholas",那么是不影响他们指针的指向的,他们依然指向Object,那么既然他们还引用同一个对象, 那么属性当然共享,所以B也会有name属性。但是现在如果有一句B = {name:Lisa},这句语句代表什么呢?其实可以拆开来看:
var C = {name:Lisa};
B = C;
现在B已经不再指向A指向的那个Object了,他现在指向C指向的Object1,既然指针都变了,那么这次改变B, 当然对A毫无影响,A还是指向Object,有自己的name属性Nicholas而非Lisa,但是B和C的name属性将是Lisa, 而且如果之后再有属性上的变化,他们也会一起变。
接下来还有最后一个问题
在我自己的测试下,我感觉在函数传参是按值传递和按共享传递(与按引用传递无关)这句话并不完整。 我觉得更完整的说法是,在JavaScript中,其实并没有按引用传递这种说法,我们认为的按引用传递,事实上都是按共享传递
再来一个简陋的例子
var a = {name:1};
var b = a;
console.log(a.name);//1
console.log(b.name);//1
b.name = 3;
console.log(a.name);//3
a = {value:100};
console.log(b.name);//3
console.log(a.name);//undefined
简单的变量赋值也和在函数中参数的表现一样,所以我觉得在JavaScript中依然只有两种数据传递方式:基本类型数据按值传递, 引用类型数据(如Object)按共享传递。