Копировать массив по значению

При копировании массива в JavaScript в другой массив:

var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.push('d');  //Now, arr1 = ['a','b','c','d']

Я понял, что arr2 относится к тому же массиву, что и arr1, а не новый, независимый массив. Как я могу скопировать массив, чтобы получить два независимых массива?

40 ответов

Решение

Использовать этот:

var newArray = oldArray.slice();

В основном, slice() Операция клонирует массив и возвращает ссылку на новый массив. Также обратите внимание, что:

Для ссылок, строк и чисел (а не фактического объекта), slice() копирует ссылки на объекты в новый массив. И исходный, и новый массив ссылаются на один и тот же объект. Если ссылочный объект изменяется, изменения видны как новому, так и исходному массивам.

Примитивы, такие как строки и числа, являются неизменяемыми, поэтому изменения строки или числа невозможны.

В Javascript методы глубокого копирования зависят от элементов в массиве.
Давайте начнем там.

Три типа элементов

Элементами могут быть: буквальные значения, буквенные структуры или прототипы.

// Literal values (type1)
const booleanLiteral = true;
const numberLiteral = 1;
const stringLiteral = 'true';

// Literal structures (type2)
const arrayLiteral = [];
const objectLiteral = {};

// Prototypes (type3)
const booleanPrototype = new Bool(true);
const numberPrototype = new Number(1);
const stringPrototype = new String('true');
const arrayPrototype = new Array();
const objectPrototype = new Object(); # or "new function () {}"

Из этих элементов мы можем создать три типа массивов.

// 1) Array of literal-values (boolean, number, string) 
const type1 = [true, 1, "true"];

// 2) Array of literal-structures (array, object)
const type2 = [[], {}];

// 3) Array of prototype-objects (function)
const type3 = [function () {}, function () {}];

Методы глубокого копирования зависят от трех типов массивов

Основываясь на типах элементов в массиве, мы можем использовать различные методы для глубокого копирования.

Техника глубокого копирования Javascript по типам элементов

  • Массив литеральных значений (type1)
    [...myArray], myArray.splice(0), myArray.slice(), а также myArray.concat() методы могут использоваться для глубокого копирования массивов только с литеральными значениями (булево, число и строка); где оператор Spread [...myArray] имеет лучшую производительность ( https://measurethat.net/Benchmarks/Show/4281/0/spread-array-performance-vs-slice-splice-concat).

  • Массив литеральных значений (тип1) и литеральных структур (тип2)
    JSON.parse(JSON.stringify(myArray)) Техника может быть использована для глубокого копирования литеральных значений (логическое, число, строка) и литеральных структур (массив, объект), но не объекты-прототипы.

  • Все массивы (тип1, тип2, тип3)
    JQuery $.extend(myArray) Техника может быть использована для глубокого копирования всех типов массивов. Такие библиотеки, как Underscore и Lo-dash, предлагают функции глубокого копирования, аналогичные jQuery.$.extend(), но имеют более низкую производительность. Более удивительно, $.extend() имеет более высокую производительность, чем JSON.parse(JSON.stringify(myArray)) техника http://jsperf.com/js-deep-copy/15.
    И для тех разработчиков, которые избегают сторонних библиотек (таких как jQuery), вы можете использовать следующую пользовательскую функцию; который имеет более высокую производительность, чем $.extend, и копирует все массивы.

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

Итак, чтобы ответить на вопрос...

Вопрос

var arr1 = ['a','b','c'];
var arr2 = arr1;

Я понял, что arr2 относится к тому же массиву, что и arr1, а не к новому независимому массиву. Как я могу скопировать массив, чтобы получить два независимых массива?

Ответ

Так как arr1 представляет собой массив литеральных значений (логические, числовые или строковые), вы можете использовать любую технику глубокого копирования, описанную выше, где оператор распространения ... имеет высочайшую производительность.

// Highest performance for deep copying literal values
arr2 = [...arr1];

// Any of these techniques will deep copy literal values as well,
//   but with lower performance.
arr2 = arr1.slice();
arr2 = arr1.splice(0);
arr2 = arr1.concat();
arr2 = JSON.parse(JSON.stringify(arr1));
arr2 = $.extend(true, [], arr1); // jQuery.js needed
arr2 = _.extend(arr1); // Underscore.js needed
arr2 = _.cloneDeep(arr1); // Lo-dash.js needed
arr2 = copy(arr1); // Custom-function needed - as provided above

Вы можете использовать массивы спредов ... копировать массивы.

const itemsCopy = [...items];

Также, если вы хотите создать новый массив с существующим, являющимся его частью:

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];

Распространения массива теперь поддерживаются во всех основных браузерах, но если вам нужна более старая поддержка, используйте машинопись или babel и компилируйте в ES5.

Больше информации о спредах

Нет необходимости в jQuery... Рабочий пример

var arr2 = arr1.slice()

Это копирует массив из начальной позиции 0 через конец массива.

Важно отметить, что он будет работать, как и ожидалось, для примитивных типов (строка, число и т. Д.), А также объяснить ожидаемое поведение для ссылочных типов...

Если у вас есть массив ссылочных типов, скажем, типа Object, Массив будет скопирован, но оба массива будут содержать ссылки на один и тот же Object"S. Таким образом, в этом случае может показаться, что массив копируется по ссылке, хотя массив фактически копируется.

Вот как я сделал это после попытки многих подходов:

var newArray = JSON.parse(JSON.stringify(orgArray));

Это создаст новую глубокую копию, не связанную с первой (не мелкую копию).

Также это, очевидно, не будет клонировать события и функции, но хорошо, что вы можете сделать это в одной строке, и это может быть использовано для любого типа объекта (массивы, строки, числа, объекты...)

Альтернатива sliceявляется concat, который можно использовать двумя способами. Первый из них, возможно, более читабелен, так как предполагаемое поведение очень ясно:

var array2 = [].concat(array1);

Второй метод:

var array2 = array1.concat();

Коэн (в комментариях) отметил, что этот последний метод имеет лучшую производительность.

Это работает так, что concat Метод создает новый массив, состоящий из элементов объекта, для которого он вызывается, за которым следуют элементы любых массивов, переданных ему в качестве аргументов. Поэтому, когда аргументы не передаются, он просто копирует массив.

Ли Пенкман, также в комментариях, указывает, что если есть шанс array1 является undefined, вы можете вернуть пустой массив следующим образом:

var array2 = [].concat(array1 || []);

Или для второго метода:

var array2 = (array1 || []).concat();

Обратите внимание, что вы также можете сделать это с slice: var array2 = (array1 || []).slice();,

Важный!

Большинство ответов здесь работает для конкретных случаев.

Если вам не нужны глубокие / вложенные объекты и реквизит (ES6):

let clonedArray = [...array]

но если вы хотите сделать глубокий клон, используйте вместо этого:

let cloneArray = JSON.parse(JSON.stringify(array))


Для пользователей lodash:

let clonedArray = _.clone(array) документация

а также

let clonedArray = _.cloneDeep(array) документация

Лично я считаю Array.from более читабельным решением. Кстати, просто остерегайтесь его поддержки браузера.

//clone
let x = [1,2,3];
let y = Array.from(x);

//deep clone
let clone = arr => Array.from(arr,item => Array.isArray(item) ? clone(item) : item);
let x = [1,[],[[]]];
let y = clone(x);

От ES2015,

var arr2 = [...arr1];

Некоторые из упомянутых методов хорошо работают при работе с простыми типами данных, такими как число или строка, но когда массив содержит другие объекты, эти методы не работают. Когда мы пытаемся передать любой объект из одного массива в другой, он передается как ссылка, а не как объект.

Добавьте следующий код в ваш файл JavaScript:

Object.prototype.clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (i in this) {
        if (i == 'clone') 
            continue;
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        } 
        else 
            newObj[i] = this[i]
    } return newObj;
};

И просто использовать

var arr1 = ['val_1','val_2','val_3'];
var arr2 = arr1.clone()

Это будет работать.

Если вы находитесь в среде ECMAScript 6, используя Spread Operator, вы можете сделать это следующим образом:

var arr1 = ['a','b','c'];
var arr2 = [...arr1]; //copy arr1
arr2.push('d');

console.log(arr1)
console.log(arr2)
<script src="http://www.wzvang.com/snippet/ignore_this_file.js"></script>

Примитивные значения всегда передаются по его значению (копируются). Составные значения, однако, передаются по ссылке.

Так, как мы копируем этот arr?

let arr = [1,2,3,4,5];

Скопируйте массив в ES6

let arrCopy = [...arr]; 

Скопируйте Массив в ES5

let arrCopy = arr.slice(); 
let arrCopy = [].concat(arr);

Почему `let arrCopy = arr` не передается по значению?

Передача одной переменной в другую по составным значениям, таким как объект / массив, ведет себя по-разному. Используя оператор asign для значений copand, мы передаем ссылку на объект. Вот почему значение обоих массивов изменяется при удалении / добавлении элементов arr.

Exceptios:

arrCopy[1] = 'adding new value this way will unreference';

Когда вы присваиваете новое значение переменной, вы изменяете саму ссылку, и это не влияет на исходный объект / массив.

прочитайте больше

Добавление к решению array.slice(); Имейте в виду, что если у вас есть многомерный массив, под-массивы будут скопированы по ссылкам. Что вы можете сделать, так это зациклить и нарезать () каждый под-массив отдельно

var arr = [[1,1,1],[2,2,2],[3,3,3]];
var arr2 = arr.slice();

arr2[0][1] = 55;
console.log(arr2[0][1]);
console.log(arr[0][1]);

function arrCpy(arrSrc, arrDis){
 for(elm in arrSrc){
  arrDis.push(arrSrc[elm].slice());
}
}

var arr3=[];
arrCpy(arr,arr3);

arr3[1][1] = 77;

console.log(arr3[1][1]);
console.log(arr[1][1]);

То же самое относится к массиву объектов, они будут скопированы по ссылке, вы должны скопировать их вручную

Я бы лично предпочел этот способ JSON.parse(JSON.stringify( originalObject));

let a = [1,2,3];

Теперь вы можете выполнить любое из следующих действий, чтобы сделать копию массива.

let b = Array.from(a); 

ИЛИ ЖЕ

let b = new Array(...a); 

ИЛИ ЖЕ

let b = a.slice(); 

ИЛИ ЖЕ

let b = a.map(e => e);

Теперь, если я изменю,

a.push(5); 

Тогда a равно [1,2,3,5], но b по-прежнему [1,2,3], поскольку имеет разностную ссылку.

Но я думаю, что во всех описанных выше методах Array.from лучше и сделан в основном для копирования массива.

Я нашел этот метод сравнительно проще:

Вы должны использовать передовую практику для этого вопроса, когда есть много ответов.

Я рекомендую вам использовать развороты массивов… для копирования массивов.

var arr1 = ['a','b','c'];

var arr2 = […arr1];

Как мы знаем в массивах и объектах Javascript, это ссылки, но как мы можем скопировать массив, не изменяя исходный массив позже?

Вот несколько способов сделать это:

Представьте, что в вашем коде есть этот массив:

var arr = [1, 2, 3, 4, 5];

1) Цикл по массиву в функции и возврат нового массива, например:

 function newArr(arr) {
      var i=0, res = [];
      while(i<arr.length){
       res.push(arr[i]);
        i++;
       }
   return res;
 }

2) Используя метод slice, slice предназначен для нарезки части массива, он будет нарезать некоторую часть вашего массива, не касаясь оригинала, в срезе, если не указать начало и конец массива, он будет нарезать весь массив массив и в основном сделать полную копию массива, поэтому мы можем легко сказать:

var arr2 = arr.slice(); // make a copy of the original array

3) Также контактный метод, это для объединения двух массивов, но мы можем просто указать один из массивов, и тогда это в основном сделает копию значений в новом контактируемом массиве:

var arr2 = arr.concat();

4) Также метод stringify и parse, не рекомендуется, но может быть простым способом скопировать массив и объекты:

var arr2 = JSON.parse(JSON.stringify(arr));

5) Метод Array.from, это не поддерживается широко, перед использованием проверьте поддержку в разных браузерах:

const arr2 = Array.from(arr);

6) Способ ECMA6, также не полностью поддерживается, но babelJs может помочь вам, если вы хотите провести:

const arr2 = [...arr];

Дэн, не нужно использовать причудливые трюки. Все, что вам нужно сделать, это сделать копию arr1, выполнив это.

var arr2 = new Array(arr1);

Сейчас arr1 а также arr2 две разные переменные массива хранятся в отдельных стеках. Проверьте это на jsfiddle.

Вы можете использовать ES6 с распространением Opeartor, это проще.

arr2 = [...arr1];

Существуют ограничения. Проверьте документы. Синтаксис распространения @ mozilla.

В моем конкретном случае мне нужно было убедиться, что массив остался нетронутым, так что это сработало:

// Empty array
arr1.length = 0;
// Add items from source array to target array
for (var i = 0; i < arr2.length; i++) {
    arr1.push(arr2[i]);
}

Сделайте копию многомерного массива / объекта:

function deepCopy(obj) {
   if (Object.prototype.toString.call(obj) === '[object Array]') {
      var out = [], i = 0, len = obj.length;
      for ( ; i < len; i++ ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   if (typeof obj === 'object') {
      var out = {}, i;
      for ( i in obj ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   return obj;
}

Спасибо Джеймсу Падолси за эту функцию.

Источник: Здесь

Если ваш массив содержит элементы типа данных примитива, такие как int, char или string и т. Д., То вы можете использовать один из тех методов, который возвращает копию исходного массива, например.slice() или.map() или оператор распространения (спасибо ES6).

new_array = old_array.slice()

или же

new_array = old_array.map((elem) => elem)

или же

const new_array = new Array(...old_array);

НО, если ваш массив содержит сложные элементы, такие как объекты (или массивы) или несколько вложенных объектов, то вам нужно будет убедиться, что вы делаете копию всех элементов с верхнего уровня до последнего уровня, а также ссылки на внутренний объекты будут использоваться, а это означает, что изменение значений в object_elements в new_array все равно повлияет на old_array. Вы можете назвать этот метод копирования на каждом уровне как создание ГЛУБОКОЙ КОПИИ old_array.

Для глубокого копирования вы можете использовать вышеупомянутые методы для примитивных типов данных на каждом уровне в зависимости от типа данных, или вы можете использовать этот дорогостоящий метод (упомянутый ниже) для создания глубокой копии без особой работы.

var new_array = JSON.parse(JSON.stringify(old_array));

Есть много других методов, которые вы можете использовать в зависимости от ваших требований. Я упомянул только некоторые из них, чтобы дать общее представление о том, что происходит, когда мы пытаемся скопировать массив в другой по значению.

Когда мы хотим скопировать массив с помощью оператора присваивания (=) он не создает копию, он просто копирует указатель / ссылку на массив. Например:

const oldArr = [1,2,3];

const newArr = oldArr;  // now oldArr points to the same place in memory 

console.log(oldArr === newArr);  // Points to the same place in memory thus is true

const copy = [1,2,3];

console.log(copy === newArr);  // Doesn't point to the same place in memory and thus is false

Часто, когда мы преобразуем данные, мы хотим сохранить исходную структуру данных (например, массив) нетронутой. Мы делаем это путем создания точной копии нашего массива, чтобы его можно было преобразовать, в то время как исходный остается неизменным.

Способы копирования массива:

const oldArr = [1,2,3];

// Uses the spread operator to spread out old values into the new array literal
const newArr1 = [...oldArr];

// Slice with no arguments returns the newly copied Array
const newArr2 = oldArr.slice();

// Map applies the callback to every element in the array and returns a new array
const newArr3 = oldArr.map((el) => el);

// Concat is used to merge arrays and returns a new array. Concat with no args copies an array
const newArr4 = oldArr.concat();

// Object.assign can be used to transfer all the properties into a new array literal
const newArr5 = Object.assign([], oldArr);

// Creating via the Array constructor using the new keyword
const newArr6 = new Array(...oldArr);

// For loop
function clone(base) {
 const newArray = [];
    for(let i= 0; i < base.length; i++) {
     newArray[i] = base[i];
 }
 return newArray;
}

const newArr7 = clone(oldArr);

console.log(newArr1, newArr2, newArr3, newArr4, newArr5, newArr6, newArr7);

Будьте осторожны, когда массивы или объекты вложены!:

Когда массивы вложены, значения копируются по ссылке. Вот пример того, как это может привести к проблемам:

let arr1 = [1,2,[1,2,3]]

let arr2 = [...arr1];

arr2[2][0] = 5;  // we change arr2

console.log(arr1);  // arr1 is also changed because the array inside arr1 was copied by reference

Поэтому не используйте эти методы, когда в вашем массиве есть объекты или массивы, которые вы хотите скопировать. т.е. использовать эти методы только для массивов примитивов.

Если вы хотите глубоко клонировать массив javascript, используйте JSON.parse в сочетании с JSON.stringify, как это:

let arr1 = [1,2,[1,2,3]]

let arr2 = JSON.parse(JSON.stringify(arr1)) ;

arr2[2][0] = 5;

console.log(arr1);  // now I'm not modified because I'm a deep clone

Выполнение копирования:

Итак, какой из них мы выбираем для оптимальной производительности. Оказывается, что наиболее многословный метод, for Цикл имеет высочайшую производительность. Использовать for цикл для действительно интенсивного копирования процессора (большой / много массивов).

После этого .slice() Метод также имеет приличную производительность, а также менее многословен и прост для реализации программистом. Я предлагаю использовать .slice() для ежедневного копирования массивов, которые не сильно нагружают процессор. Также избегайте использования JSON.parse(JSON.stringify(arr)) (много накладных расходов), если глубокое клонирование не требуется и производительность является проблемой.

Тест производительности источника

структурированныйклон

Современный способ копирования массива по значению в JavaScript — использоватьstructuredClone:

var arr2 = arr1.slice(0);

Этот способ работает только для простых массивов .

Если у вас есть сложный массив, такой как массив объектов , вы должны использовать другие решения, такие как:

      const arr2 = JSON.parse(JSON.stringify(arr1)); 

Например, у нас есть массив объектов, в котором каждая ячейка имеет другое поле массива в своем объекте... в этой ситуации, если мы используем метод среза , поля массива будут скопированы с помощью Ref , и это означает, что обновления этих полей будут влиять на исходный массив так же . элемент и поля.

Если вы хотите создать новую копию объекта или массива, вы должны явно скопировать свойства объекта или элементов массива, например:

var arr1 = ['a','b','c'];
var arr2 = [];

for (var i=0; i < arr1.length; i++) {
   arr2[i] = arr1[i];
}

Вы можете найти в Google дополнительную информацию об неизменных значениях примитивов и ссылках на изменяемые объекты.

Для массива ES6, содержащего объекты

cloneArray(arr) {
    return arr.map(x => ({ ...x }));
}

Используя глубокое копирование jQuery, можно сделать следующее:

var arr2 = $.extend(true, [], arr1);

Вот еще несколько способов копирования:

const array = [1,2,3,4];

const arrayCopy1 = Object.values(array);
const arrayCopy2 = Object.assign([], array);
const arrayCopy3 = array.map(i => i);
const arrayCopy4 = Array.of(...array );

Другие вопросы по тегам