2015-11-17 34 views
4

我发现了一个奇怪的JS行为:奇怪的JavaScript阵列同步技巧?

var myArray = [ [1] , [2] , [3] ]; 

var myArrayCopy = []; 

myArrayCopy.push(myArray[1]); 

alert(myArrayCopy); // 2, as expected. 

myArrayCopy[0][0] = 'foo'; 

alert(myArrayCopy); // 'foo', as expected. 

alert(myArray); // 1, foo, 3 = WTF ? :) 

Demo

请注意,如果我们直接使用值而不是数组,则这不起作用。

对我来说,它看起来像将数组推到数组中,翻译成某种方式,就好像我们只推送那些数组的引用,而不是副本,(这不是人们所期待的行为,如果我错了,请更正) 。

有人可以解释为什么吗?

+1

数组传递给Ref,它不像你编辑一个ref,你正在编辑一个ref。你可以使用.slice()来获得一个具有相同值的新容器(浅拷贝) – dandavis

+1

@dandavis - 你的评论在概念上是正确的,但不准确。在JavaScript中没有* byRef *这样的东西,但是传递一个对象(数组* *)会导致你描述的效果。 – Amit

+0

没有争论的语义,在js中,它与可变/不可变的数字,字符串,布尔值是不可变的,因此通过值传递,而可变值,又名对象,作为对象引用传递。是迂腐的,objs在技术上并没有通过ref传递,它是一个指向ref的指针,通过值传递,但这只是令人困惑,其效果是传递“byRef”... – dandavis

回答

2

别名。

您在myArrayCopy中引用了一个对myArray中包含的存在对象(数组)的引用。因此,当您修改该数组时,您正在修改从上述数组中引用的实际对象。

请注意,当您在JavaScript中传递对象时,它们不会被复制,而是传递给实例的引用并在接收器的范围内修改它将导致修改后的对象位于来电者也是如此。 这条规则有一些明显的例外(作为原始类型的示例,它们不是对象,或者是在写入时实际复制的字符串等等),但让我们专注于您的问题。

考虑到一个数组是一个对象,它发生在下面的例子一样,但它是从我的观点更加清晰:

var o = { "foo": " bar" }; 
myArray[0] = o; 
myArrayCopy[0] = myArray[0]; 
o.foo = "nolongerbar"; 

是什么返回myArrayCopy[0].foo?什么myArray[0].foo

在大多数OO语言中称为别名,无论如何,这是处理对象引用时的常见错误。

编辑

哦,这不是在任何情况下同步的把戏,它通常是恼人的bug背后的原因。 :-)

+0

谢谢你的完整答案。特别是为了指出“别名”这个术语! – lapin

+0

不客气。这是一个广泛使用的术语,尽管在关于JavaScript的讨论中发现它并不常见。我不知道为什么,也许存在一个具有相同含义的术语,但我发现* aliasing *是最有意义的(至少对我来说当然是)。 – skypjack

2

myArray[1]的值是一个数组([2])。当您将其推入myArrayCopy.push(myArray[1]);中的myArrayCopy时,您将按数组,而不是它的内容([2]而不是2)。

当你稍后改变这个数组(myArrayCopy[0][0] = 'foo';)时,它在使用这个数组的任何地方显然都有效果。

+0

我知道推送一个数组而不是它的内容,我的问题是,当我更改myArrayCopy [0] [0]值时,它是如何“明显”,它对第一个数组也有影响!但我认为其他答案可能已经回答了:) – lapin

+0

@lapin - 这是相同的数组。这就是我在我的回答中试图解决的问题:*你推动数组,而不是内容*和*当你稍后改变这个数组*。这一直是同一阵列。 – Amit

0

是的,这是有道理的行动,因为myArray 1和myArrayCopy [0]具有相同的内存地址。您可以检查以下调试捕捉

enter image description here

通过线9号之后,那些已经在同一时间myArray的改变,myArraycopy的价值,是“富”

-2

这是完全正确的。

由于数组作为对象,它存储内存地址 而不是存储值本身。

var myArray = [ [1] , [2] , [3] ]; 

myArray将存储创建的数组的地址(比如addr-1)。

由于阵列中的元素本身是一个阵列,它现在将具有对应于阵列[1][2]三个地址,分别[3]addr-2addr-3addr-4

所以最后我们:

addr-1 = [addr-2,addr-3,addr-4] 

var myArrayCopy = []; 

这将存储一个新的地址,说addr-5 = []

myArrayCopy.push(myArray[1]); 

,因为你推的myArray的第一个元素myArrayCopy。它将存储地址addr-3。这样addr-5 = [addr-3]

`alert(myArrayCopy); // 2, as expected 

所以当然这工作正常为addr-3 = [2]

myArrayCopy[0][0] = 'foo'; 

这里要修改的myArrayCopy零个元素的零个元素的值,即你在addr-3改变的第一要素到foo

alert(myArrayCopy); // 'foo', as expected 

所以它的工作原理,以及:

alert(myArray); // 1, foo, 3 

myArray现在有addr-1 = [addr-2, addr-3, addr-4],其中addr-2 = [1]addr-3 = ["foo"]addr-4 = [3]

+1

你的回答看起来像是它的格式吗? (看看周围,人群中***非常奇怪) – Amit

+0

我知道它不是,我第一次接电话,所以我只是在玩耍。 –