奇怪的Javascript数组同步技巧?
我发现了一个奇怪的JS行为:奇怪的Javascript数组同步技巧?,javascript,arrays,html,pointers,Javascript,Arrays,Html,Pointers,我发现了一个奇怪的JS行为: 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
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 ? :)
看
请注意,如果我们直接推送值而不是数组,这将不起作用
对我来说,这看起来像是将数组推入一个数组,以某种方式转换为只将引用推入那些数组,而不是副本(这不是人们所期望的行为,如果我错了,请更正)
有人能解释为什么吗?别名
您正在输入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背后的原因。:-) myArray[1]的值是一个数组(
[2]
)。在myArrayCopy.push(myArray[1])中将其推入myArrayCopy
时
,您推送数组,而不是它的内容([2]
,而不是2
)
当您稍后对该数组进行变异时(
myArrayCopy[0][0]='foo';
),它显然会影响使用该数组的所有地方。是的,这是有意义的操作,因为myArray和myArrayCopy[0]具有相同的内存地址。您可以检查下面的调试捕获
在通过第9行之后,它们同时被myArray和myArraycopy的值更改为“foo”这是完全正确的 由于数组充当对象,因此它存储内存地址 而不是存储值本身
var myArray = [ [1] , [2] , [3] ];
myArray
将存储所创建数组的地址(比如addr-1
)
由于数组的元素本身就是一个数组,它现在将有三个地址分别对应于数组[1]
,[2]
,[3]
比如addr-2
,addr-3
,addr-4
因此,最后我们有:
addr-1 = [addr-2,addr-3,addr-4]
这将存储一个新地址,例如
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
的第0个元素的第0个元素的值,即将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]
数组是通过ref传递的,这并不是说你在编辑一个ref,而是在编辑一个ref。你可以使用.slice()获得一个具有相同值的新容器(浅拷贝)@dandavis-你的注释在概念上是正确的,但不准确。JavaScript中没有byRef这样的东西,但是传递一个对象(即数组)会产生您所描述的效果。在js中,它与可变/不可变的内容保持一致:数字、字符串、布尔值是不可变的,因此按值传递,而可变的对象(也称为对象)则作为对象引用传递。迂腐地说,从技术上讲,obj不是通过ref传递的,它是一个指向ref的指针,通过值传递,但这只是混淆,其效果是传递“byRef”…@dandavis不,这不是真的。同样,ByRef是VB。(类似于代码>空白f(int v);C++中的,和<代码>空腹F(REF INTV);中的<代码>,但是这导致调用函数时使用的变量(地址)的引用(“传入”),实际上让函数改变了外部变量。JavaScript所做的更像是void f(int v)
或void f(obj*v)
,在第二个函数中使用对象的地址,并且可以从外部看到对象的变化。我猜OP实际上对js如何在引擎盖下工作以及它的相似性不感兴趣。从他的问题来看,他似乎仍在触及语言的表面。:-)我知道推送一个数组而不是它的内容,我的问题是,当我更改值myArrayCopy[0][0]时,它对第一个数组的影响如何“明显”!但我认为其他答案可能已经回答了这个问题:)@lapin-这是相同的数组。这就是我在回答中试图解决的问题:你推送数组,而不是它的内容,当你以后改变这个数组时。它一直都是同一个数组。你的答案看起来像它应该的格式吗?(环顾四周,在人群中很奇怪)我知道不是,我是第一次回答,所以我只是随便玩玩。谢谢你完整的回答。特别是为了指出“别名”这一术语!不客气。这是一个广泛使用的术语,尽管在关于JavaScript的讨论中提到它并不常见。我不知道为什么,也许有一个术语具有相同的含义,但我发现别名是最有意义的(当然,至少对我来说是如此)。
alert( myArray ); // 1, foo, 3