JavaScriptの配列を別の配列にコピーする場合
var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.Push('d'); //Now, arr1 = ['a','b','c','d']
arr2
は、新しい独立した配列ではなく、arr1
と同じ配列を指すことに気付きました。 2つの独立した配列を取得するために配列をコピーする方法を教えてください。
これを使って:
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 () {}"
これらの要素から、3種類の配列を作成できます。
// 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 () {}];
配列内の要素の種類に基づいて、ディープコピーにさまざまな手法を使用できます。
リテラル値の配列(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 ).
リテラル値(type1)とリテラル構造体(type2)の配列 JSON.parse(JSON.stringify(myArray))
テクニックはリテラル値(ブール値、数値、文字列)とリテラル構造体(配列、オブジェクト)をディープコピーするのに使用できますが、プロトタイプオブジェクトはコピーできません。
すべての配列(type1、type2、type3)
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と同じ配列を参照していることに気付きました。 2つの独立した配列を取得するために配列をコピーする方法を教えてください。
答え
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'];
配列スプレッドは現在 すべての主要ブラウザでサポートされています しかし古いサポートが必要な場合はTypeScriptまたはbabelを使用してES5にコンパイルしてください。
JQueryは必要ありません...作業例
var arr2 = arr1.slice()
これにより、配列が開始位置0
から配列の最後までコピーされます。
プリミティブ型(文字列、数値など)で期待どおりに機能することに注意し、参照型で期待される動作を説明することも重要です...
たとえばObject
タイプの参照タイプの配列がある場合。配列はコピーされますが、両方の配列に同じObject
の参照が含まれます。したがって、この場合、配列が実際にコピーされていても、配列は参照によってコピーされているように見えます。
slice
の代替は concat
で、これは2つの方法で使用できます。意図された動作が非常に明確であるので、これらのうちの最初のものはおそらくもっと読みやすいです:
var array2 = [].concat(array1);
2番目の方法は次のとおりです。
var array2 = array1.concat();
Cohen氏(コメント)は、この後者の方法 がより優れたパフォーマンス を持つことを指摘しました。
これが機能する方法は、concat
メソッドが、それが呼び出されたオブジェクト内の要素とそれに続く引数として渡された任意の配列の要素から構成される新しい配列を作成することです。そのため、引数が渡されなかった場合は、単純に配列をコピーします。
Lee Penkmanもコメントで、array1
がundefined
である可能性があるなら、次のように空の配列を返すことができると指摘しています。
var array2 = [].concat(array1 || []);
または、2番目の方法では:
var array2 = (array1 || []).concat();
slice
:var array2 = (array1 || []).slice();
でもこれを行うことができます。
これは私が多くのアプローチを試みた後それをやった方法です:
var newArray = JSON.parse(JSON.stringify(orgArray));
これにより、最初のコピーとは無関係の(ディープコピーではない)新しいディープコピーが作成されます。
また、これは明らかにイベントや関数を複製することはありませんが、1行でそれを行うことができ、それはあらゆる種類のオブジェクト(配列、文字列、数値、オブジェクト...)に使用できます
上記のメソッドの中には、numberやstringのような単純なデータ型を扱うときにはうまく機能するものもありますが、配列に他のオブジェクトが含まれているとこれらのメソッドは失敗します。ある配列から別の配列にオブジェクトを渡そうとすると、オブジェクトとしてではなく参照として渡されます。
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()
それが動作します。
ES2015から、
var arr2 = [...arr1];
私は個人的には 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);
重要!
ここでの答えのほとんどは特定の場合に有効です。
深い/ネストしたオブジェクトや小道具を気にしないのであれば( ES6 )を使ってください。
let clonedArray = [...array]
しかし、もしあなたがディープクローンをしたいのなら、代わりにこれを使ってください:
let cloneArray = JSON.parse(JSON.stringify(array))
lodashユーザーの場合:
let clonedArray = _.clone(array)
ドキュメンテーション
そして
let clonedArray = _.cloneDeep(array)
ドキュメンテーション
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>
array.slice();の解を追加すると、多次元配列サブ配列がある場合は参照によってコピーされます。各サブ配列を個別にループして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]);
同じことがオブジェクトの配列にも当てはまります。それらは参照によってコピーされます。手動でコピーする必要があります
Javascriptで知っているように、 array と objects は参照によるものですが、後で元の配列を変更せずに配列をコピーするにはどうすればよいでしょうか。
これを行うにはいくつかの方法があります。
あなたのコードにこの配列があると想像してみてください。
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)スライス法を使用して、スライスはアレイの一部をスライスするためのものです、それはスライスの元の部分に触れることなくあなたのアレイの一部をスライスします。配列と基本的に配列の完全なコピーを作るので、我々は簡単に言うことができる:
var arr2 = arr.slice(); // make a copy of the original array
3)contactメソッドも、これは2つの配列をマージするためのものですが、配列の1つを指定するだけで、その後、基本的に新しいcontact配列の値のコピーが作成されます。
var arr2 = arr.concat();
4)メソッドの文字列化と解析も推奨されませんが、ArrayとObjectsをコピーする簡単な方法になります。
var arr2 = JSON.parse(JSON.stringify(arr));
5)Array.fromメソッド、これは広く使用されていません、使用する前に別のブラウザでサポートを確認してください:
const arr2 = Array.from(arr);
6)ECMA6の方法も、完全にはサポートされていませんが、変換したい場合はbabelJsが役に立ちます。
const arr2 = [...arr];
多次元配列/オブジェクトのコピーを作成します。
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;
}
この機能を提供してくれたJames Padolseyに感謝します。
出典: ここ
私の特定のケースでは、アレイが元の状態のままであることを確認する必要がありました。
// 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]);
}
代入演算子(=
)を使って配列をコピーしたいときは、コピーを作成するのではなく、単に配列へのポインタ/参照をコピーします。例えば:
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
多くの場合、データを変換するときに、最初のデータ構造(Arrayなど)をそのままにしておきます。これは、最初の配列がそのままの状態で変換できるように、配列の正確なコピーを作成することによって行います。
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
ループが最高のパフォーマンスを発揮することがわかりました。本当にCPUに負荷をかけるコピー(大規模/多数の配列)にはfor
ループを使用してください。
その後、.slice()
メソッドはまた、まともなパフォーマンスを持っていて、プログラマーにとってはあまり冗長ではなく、そして実装も簡単です。私は.slice()
をあなたの毎日の配列のコピーに使うことを勧めます。また、ディープクローンが不要でパフォーマンスに問題がある場合は、JSON.parse(JSON.stringify(arr))
(多くのオーバーヘッド)の使用を避けてください。
ダン、派手なトリックを使う必要はありません。必要なのは、これを実行してarr1のコピーを作成することだけです。
var arr2 = new Array(arr1);
arr1
とarr2
は、別々のスタックに格納された2つの異なる配列変数です。 jsfiddleでこれを確認してください 。
配列にint、char、またはstringなどなどのprimitive data typeの要素が含まれる場合、次のような元の配列のコピーを返すメソッドのいずれかを使用できます。 .slice()または.map()またはスプレッド演算子として(ES6に感謝)。
new_array = old_array.slice()
または
new_array = old_array.map((elem) => elem)
または
const new_array = new Array(...old_array);
BUT配列に含まれている場合複雑な要素オブジェクト(または配列)以上ネストされたオブジェクトの場合、確認する必要がありますトップレベルから最後のレベルまでのすべての要素のコピーを作成します。そうしないと、内部オブジェクトの参照が使用されます。つまり、new_arrayのobject_elementsの値を変更しても、old_arrayに影響します。 old_arrayのDEEP COPYを作成することで、各レベルでこのコピー方法を呼び出すことができます。
ディープコピーの場合、データのタイプに応じて各レベルでプリミティブデータタイプに対して上記のメソッドを使用できます。または、これを使用して、コストのかかるメソッド(下記)仕事いっぱい。
var new_array = JSON.parse(JSON.stringify(old_array));
要件に応じて使用できる方法は他にもたくさんあります。配列を他の値によるにコピーしようとすると何が起こるかについての一般的なアイデアを与えるために、それらのいくつかだけを言及しました。
コピーするもう少し方法があります。
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 );
簡単な例:
var arr1 = ['a','b','c'];
// arr1 and arr2 are independent and primitive elements are stored in
// different places in the memory
var arr2 = arr1.slice();
arr2.Push('d');
console.log(arr1); // [ 'a', 'b', 'c' ]
console.log(arr2); // [ 'a', 'b', 'c', 'd' ]
var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
// arr1 and arr2 are independent and reference's/addresses are stored in different
// places in the memory. But those reference's/addresses points to some common place
// in the memory.
var arr2 = arr1.slice();
arr2.pop(); // OK - don't affect arr1 bcos only the address in the arr2 is
// deleted not the data pointed by that address
arr2[0].x = 'z'; // not OK - affect arr1 bcos changes made in the common area
// pointed by the addresses in both arr1 and arr2
arr2[1][0] = 9; // not OK - same above reason
console.log(arr1); // [ { x: 'z', y: 'b' }, [ 9, 2 ], [ 3, 4 ] ]
console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]
var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
arr2 = JSON.parse(JSON.stringify(arr1));
arr2.pop(); // OK - don't affect arr1
arr2[0].x = 'z'; // OK - don't affect arr1
arr2[1][0] = 9; // OK - don't affect arr1
console.log(arr1); // [ { x: 'a', y: 'b' }, [ 1, 2 ], [ 3, 4 ] ]
console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]
JQueryディープコピーを使用すると、次のようになります。
var arr2 = $.extend(true, [], arr1);
ES6のスプレッド演算子を使って配列をコピーすることもできます。
var arr=[2,3,4,5];
var copyArr=[...arr];
オブジェクトまたは配列の新しいコピーを作成する場合は、オブジェクトのプロパティまたは配列の要素を明示的にコピーする必要があります。たとえば、
var arr1 = ['a','b','c'];
var arr2 = [];
for (var i=0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
不変のプリミティブ値および可変オブジェクト参照について、Googleでさらに情報を探すことができます。
私は個人的にはこのようにJSON.parse(JSON.stringify(originalObject))を好むでしょう。
これが可変深さのプリミティブの配列の配列に対してどのようにそれをすることができるかです:
// If a is array:
// then call cpArr(a) for each e;
// else return a
const cpArr = a => Array.isArray(a) && a.map(e => cpArr(e)) || a;
let src = [[1,2,3], [4, ["five", "six", 7], true], 8, 9, false];
let dst = cpArr(src);
新しく導入されたArray.from
がありますが、残念ながら、これを書いている時点では最近のFirefoxバージョン(32以降)でしかサポートされていません。それは単に次のように使うことができます。
var arr1 = [1, 2, 3];
console.log(Array.from(arr1)); // Logs: [1, 2, 3]
参照: ここ
あるいはArray.prototype.map
を恒等関数とともに使用することもできます。
function identity(param)
{
return param;
}
var arr1 = [1, 2, 3],
clone = arr1.map(identity);
参照: ここ
次のようにしてそれを行うことができます。arr2 = arr1.map(x => Object.assign({}, x));
let a = [1,2,3];
これで、次のいずれかの方法で配列のコピーを作成できます。
let b = Array.from(a);
OR
let b = [...a];
OR
let b = new Array(...a);
OR
let b = a.slice();
OR
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=eval(arr1.toSource());
arr2.Push('d');
console.log('arr1: '+arr1+'\narr2: '+arr2);
/*
* arr1: a,b,c
* arr2: a,b,c,d
*/
ただ書いている:
arr2 = arr1.concat();
最初のコピーを使って新しい配列を新しく生成しています。これは要素を配列にプッシュする機能的な方法です。
あなたのコードがES6に基づいているなら、あなたは同様に拡散演算子を使うことができます:
arr2 = [...arr1];
オブジェクトを含むES6配列の場合
cloneArray(arr) {
return arr.map(x => ({ ...x }));
}
では、どうやってこのarrをコピーするのでしょうか。
let arr = [1,2,3,4,5];
let arrCopy = [...arr];
let arrCopy = arr.slice();
let arrCopy = [].concat(arr);
Object/Arrayなどの複合値である変数を別の変数に渡すと、動作が異なります。 copand値にasign演算子を使用して、オブジェクトへの参照を渡します。 arr要素を削除または追加すると、両方の配列の値が変化するのはこのためです。
arrCopy[1] = 'adding new value this way will unreference';
変数に新しい値を代入しても、参照自体は変更され、元のオブジェクト/配列には影響しません。