【问题标题】:Why does changing an Array in JavaScript affect copies of the array?为什么在 JavaScript 中更改数组会影响数组的副本?
【发布时间】:2011-09-30 12:59:31
【问题描述】:

我编写了以下 JavaScript:

var myArray = ['a', 'b', 'c'];
var copyOfMyArray = myArray;
copyOfMyArray.splice(0, 1);
alert(myArray); // alerts ['b','c']
alert(copyOfMyArray); // alerts ['b','c']

var myNumber = 5;
var copyOfMyNumber = myNumber;
copyOfMyNumber = copyOfMyNumber - 1;
alert(myNumber); // alerts 5
alert(copyOfMyNumber); // alerts 4        

此代码声明了一个变量myArray 并将其设置为一个数组值。然后它声明第二个变量copyOfMyArray 并将其设置为myArray。 它对copyOfMyArray 执行操作,然后向myArraycopyOfMyArray 发出警报。不知何故,当我对copyOfMyArray 执行操作时,似乎对myArray 执行了相同的操作。

然后代码对数字值执行相同的操作:它声明变量myNumber 并将其设置为数字值。然后它声明第二个变量copyOfMyNumber 并将其设置为myNumber。它对copyOfMyNumber 执行操作,然后向myNumbercopyOfMyNumber 发出警报。在这里,我得到了预期的行为:myNumbercopyOfMyNumber 的不同值。

JavaScript 中数组和数字的区别在于,更改数组似乎会更改数组副本的值,而更改数字不会更改数字副本的值?

我猜出于某种原因,数组是按引用引用的,而数字是按值引用的,但是为什么呢?我如何知道其他对象的预期行为?

【问题讨论】:

    标签: javascript


    【解决方案1】:

    JavaScript 中的数组也是一个对象,变量只保存一个对象的引用,而不是对象本身。因此,这两个变量都引用了相同的对象。

    顺便说一句,您与数字示例的比较不正确。您为copyOfMyNumber 分配了一个新值。如果您为 copyOfMyArray 分配一个新值,它也不会更改 myArray

    您可以使用slice [docs] 创建数组的副本:

    var copyOfMyArray = myArray.slice(0);
    

    但请注意,这只返回一个副本,即不会克隆数组内的对象。

    【讨论】:

    • +1 - 只是出于好奇,在这种情况下直接分配 myArray.slice(0); 有什么缺点吗?
    • @Rice:不,我只是编辑澄清一下。如果你想要一个深拷贝,你必须自己写一些东西。但我相信您会找到执行此操作的脚本。
    • @FelixKling:我没有例子。我只是问,因为你先应用了原型方法。
    • @jAndy:啊,所以你提到了那个......我只是有点困惑,最近我经常相反;)
    【解决方案2】:

    嗯,唯一可能的答案——也是正确的答案——是你实际上并没有复制数组。当你写的时候

    var copyOfArray = array;
    

    您将 reference 分配给同一个数组到另一个变量中。换句话说,它们都指向同一个对象。

    【讨论】:

    • 我会说你没有完全分配引用指针,你分配的是引用的副本。因为如果你将 obj 传递给函数并尝试用函数内的另一个新对象替换它,你将不会更改原始对象。
    • @kashesandr 是的,“分配参考”的意思是“分配参考的副本”,这是真的。然而,两个相等的引用总是相等的,就像数字 5 的两个实例总是相等的一样。
    【解决方案3】:

    所以这里的每个人都很好地解释了为什么会发生这种情况 - 我只是想写一句话让你知道如何 我能够解决这个问题 - 很容易:

    thingArray = ['first_thing', 'second_thing', 'third_thing']
    function removeFirstThingAndPreserveArray(){
      var copyOfThingArray = [...thingArray]
      copyOfThingArray.shift();
      return copyOfThingArray;
    }
    

    这是使用 ... 展开语法。

    Spread Syntax Source

    编辑:至于为什么,并回答您的问题:

    JavaScript 中数组和数字的区别在于,更改数组似乎会更改数组副本的值,而更改数字不会更改数字副本的值?

    答案是,在 JavaScript 中,数组和对象是可变的,而字符串和数字等原语是不可变的。当我们执行以下任务时:

    var myArray = ['a', 'b', 'c']; var copyOfMyArray = myArray;

    copyOfMyArray 实际上只是对 myArray 的引用,而不是实际的副本。

    我会推荐这篇文章,What are immutable and mutable data structures?,以更深入地研究这个主题。

    MDN Glossary: Mutable

    【讨论】:

    • 正是我想要的。
    【解决方案4】:

    克隆对象 -

    loop / array.push 产生与array.slice(0)array.clone() 相似的结果。值都是通过引用传递的,但由于大多数原始数据类型是不可变,因此后续操作会产生所需的结果——“克隆”。当然,对象和数组并非如此,它们允许修改原始引用(它们是可变类型)。

    举个例子:

    const originalArray = [1, 'a', false, {foor: 'bar'}]
    const newArray = [];
    
    originalArray.forEach((v, i) => {
        newArray.push(originalArray[i]);
    });
    
    newArray[0] = newArray[0] + 1;
    newArray[1] = 'b';
    newArray[2] = true;
    newArray[3] = Object.assign(newArray[3], {bar: 'foo'});
    

    在 newArray 索引上运行的操作都会产生所需的结果,除了 final (object),因为它是通过引用复制的,所以也会改变 originalArray[3]。

    https://jsfiddle.net/7ajz2m6w/

    请注意,array.slice(0) and array.clone() 也有同样的限制。

    解决此问题的一种方法是在推送序列期间有效地克隆对象:

    originalArray.forEach((v, i) => {
        const val = (typeof v === 'object') ? Object.assign({}, v) : v;
        newArray.push(val);
    });
    

    https://jsfiddle.net/e5hmnjp0/

    干杯

    【讨论】:

      【解决方案5】:

      在 JS 中,运算符“=”将指针复制到数组的内存区域。 如果要将一个数组复制到另一个数组中,则必须使用 Clone 函数。

      整数是不同的,因为它们是原始类型。

      S.

      【讨论】:

        【解决方案6】:

        浅拷贝的问题是所有对象都没有被克隆,而是被引用。所以 array.slice(0) 只适用于文字数组,但它不会对对象数组进行浅拷贝。在这种情况下,一种方法是..

        var firstArray = [{name: 'foo', id: 121}, {name: 'zoo', id: 321}];
        var clonedArray = firstArray.map((_arrayElement) => Object.assign({}, _arrayElement));  
        console.log(clonedArray);
        // [{name: 'foo', id: 121}, {name: 'zoo', id: 321}]  // shallow copy
        

        【讨论】:

          【解决方案7】:

          在arrayCopy中创建一个原始数组的过滤器。这样对新数组的更改不会影响原始数组。

          var myArray = ['a', 'b', 'c'];
          var arrayCopy = myArray.filter(function(f){return f;})
          arrayCopy.splice(0, 1);
          alert(myArray); // alerts ['a','b','c']
          alert(arrayCopy); // alerts ['b','c']
          

          希望对你有帮助。

          【讨论】:

            【解决方案8】:

            除原始数据类型(字符串和数字 IIRC)外,所有内容都是通过引用复制的。

            【讨论】:

            • 这不是真的。所有分配都分配参考。字符串和数字是不可变的。
            【解决方案9】:

            您没有任何副本。
            你有多个变量持有同一个数组。

            同样,你有多个变量持有相同的数字。

            当您编写 copyOfMyNumber = ... 时,您将一个新数字放入变量中。
            这就像写copyOfMyArray = ...

            当您编写 copyOfMyArray.splice 时,您正在修改原始数组
            数字是不可能的,因为数字是不可变的,不能修改,

            【讨论】:

              【解决方案10】:

              您可以根据您的情况添加一些错误处理,并使用类似于以下功能的内容来解决问题。请评论任何错误/问题/效率想法。

              function CopyAnArray (ari1) {
                 var mxx4 = [];
                 for (var i=0;i<ari1.length;i++) {
                    var nads2 = [];
                    for (var j=0;j<ari1[0].length;j++) {
                       nads2.push(ari1[i][j]);
                    }
                    mxx4.push(nads2);
                 }
                 return mxx4;
              }
              

              【讨论】:

                【解决方案11】:

                除非您克隆或复制,否则 javascript 中的数组或对象始终拥有相同的引用。这是一个例子:

                http://plnkr.co/edit/Bqvsiddke27w9nLwYhcl?p=preview

                // for showing that objects in javascript shares the same reference
                
                var obj = {
                  "name": "a"
                }
                
                var arr = [];
                
                //we push the same object
                arr.push(obj);
                arr.push(obj);
                
                //if we change the value for one object
                arr[0].name = "b";
                
                //the other object also changes
                alert(arr[1].name);
                

                对于对象克隆,我们可以在 jquery 中使用 .clone() 和 angular.copy(),这些函数将创建具有其他引用的新对象。如果您知道更多功能,请告诉我,谢谢!

                【讨论】:

                  【解决方案12】:

                  对于带有对象的数组,您可以更改使用 JSON.parse 和 JSON.strinigfy 将数组的类型更改为对象引用到字符串,然后返回数组,而不必担心原始数组

                  var array = [{name:'John', age:34, logs:'[]'}, {name:'David', age:43, logs:'[3]'}];
                    var array2 = JSON.parse(JSON.stringify(array)); // turn object to function output
                    array2[0].age++;
                    alert(JSON.stringify(array));
                    alert(JSON.stringify(array2));
                  

                  【讨论】:

                    猜你喜欢
                    • 2021-04-01
                    • 1970-01-01
                    • 2020-10-04
                    相关资源
                    最近更新 更多