Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
AngularJS TIPS

AngularJS TIPS

配列/オブジェクトをコピーするには?(copy)

2016年4月19日

配列のコピーで、JavaScript標準のconcatメソッドを使う場合とAngularJSのcopyメソッドを使う場合の違いを説明。シャローコピーとディープコピーとは?

  • このエントリーをはてなブックマークに追加

 配列/オブジェクトをコピーするには、AngularJSのグローバルAPIであるangular.copyメソッドを利用します。

[構文]copyメソッド

angular.copy(src [,dest])

  • src: コピー元の配列/オブジェクト
  • dest: コピー先の配列/オブジェクト

 引数srcで指定された配列/オブジェクトは、以下のルールでコピーされます。

  • 引数destが省略された場合、新たな配列/オブジェクトが生成されて、戻り値として返される
  • 引数destが明示された場合、配下の要素/プロパティを全て削除した上で、引数srcの内容をコピー
  • 引数srcが配列/オブジェクトでない場合、元の値をそのまま戻り値として返す
  • 引数srcdestが同一の配列/オブジェクトである場合、例外を発生

 では、具体的な例も見てみましょう。以下は、それぞれ配列pet1pet2に、オブジェクトperson1person2に、それぞれコピーする例です。

HTML
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="UTF-8" />
<title>AngularJS TIPS</title>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<script>
var pet1 = ['リンリン', 'トクジロウ', 'サチ'];
var pet2 = ['ニンザブロウ', 'ウタ', 'ハナ', 'キラ'];

var person1 ={
  name: '鈴木花子',
  mail: 'hanako@example.com',
  age: 26
};

var person2 ={
  name: '山田太郎',
  mail: 'taro@example.com',
  age: 27,
  married: false
};

console.log('コピー前:');
console.log(pet2);
console.log(person2);

var result_pet = angular.copy(pet1, pet2);
var result_person = angular.copy(person1, person2);

console.log('コピー後:');
console.log(pet2);
console.log(person2);
</script>
</body>
</html>
配列/オブジェクトをコピーするコード(copy.html)
コンソール
コピー前:
["ニンザブロウ", "ウタ", "ハナ", "キラ"]
{name: "山田太郎", mail: "taro@example.com", age: 27, married: false}

コピー後:
["リンリン", "トクジロウ", "サチ"]
{name: "鈴木花子", mail: "hanako@example.com", age: 26}
Chrome DevToolsの[Console]パネルで結果を表示

 1つ目のangular.copyメソッドの引数destに指定された配列pet2の末尾のキラ、および2つ目のメソッドの引数destに指定されたオブジェクトperson2marriedプロパティは、いずれもコピー処理の後では消えている――つまり、コピーに際して、引数destの内容がいったん削除されていることが確認できます。

copyメソッドはディープコピー

 配列/オブジェクトのコピーには、シャローコピーディープコピーとがあります。一般的に、シャローコピーを行うにはJavaScript標準のconcatメソッドを、ディープコピーを行うにはangular.copyメソッドを利用します。

 以下では、両者の違いを理解するために、それぞれの方法で配列をコピーしてみましょう。まずは、JavaScript標準のconcatメソッドからです。

HTML
<!DOCTYPE html>
<html ng-app>
<head>
<meta charset="UTF-8" />
<title>AngularJS TIPS</title>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<script>
// 1値型の要素を持つ配列をコピー
var src1 = [10, 20, 30];
var dest1 = src1.concat();
src1[0] = 100;
console.log('値型:');
console.log(src1);
console.log(dest1);

// 2参照型の要素を持つ配列をコピー
var src2 = [{a:1, b:2}, {a:5, b:10}, {a:10, b:100},];
var dest2 = src2.concat();
src2[0].a = 100;
console.log('参照型:');
console.log(src2);
console.log(dest2);
</script>
</body>
</html>
JavaScript標準のconcatメソッドによる配列のコピー(concat.html)
コンソール
値型:
[100, 20, 30]
[10, 20, 30]

参照型:
[{ a=100,  b=2}, { a=5,  b=10}, { a=10,  b=100}]
[{ a=100,  b=2}, { a=5,  b=10}, { a=10,  b=100}]
Chrome DevToolsの[Console]パネルで結果を表示

[参照型:]の方は[Object, Object, Object]のような表示になるので、その項目のツリーを展開することで上記のような値を確認できる。

 concatメソッドは、もともと配列を連結するためのメソッドです。ただし、引数を省略することで、現在の配列をそのまま返すこともできます。ここでは、その性質を利用して、コピーの役割を担わせているわけです。

 まず、1のケースでは問題ありません。値型の要素なので、コピー元配列への変更がコピー先配列に影響することはありません。

 しかし、2の参照型要素をコピーした場合、コピー元配列への変更はコピー先配列にも影響するので、これが問題となる場合があります。オブジェクトそのものではなく、オブジェクトの参照だけを浅く(=シャロー)コピーしているからです。

 これを、angular.copyメソッドで書き換えるとどうでしょう。

JavaScript
var dest1 = angular.copy(src1);
……中略……
var dest2 = angular.copy(src2);
AngularJSのcopyメソッドによる配列のコピー(concat.html)
コンソール
値型:
[100, 20, 30]
[10, 20, 30]

参照型:
[{ a=100,  b=2}, { a=5,  b=10}, { a=10,  b=100}]
[{ a=1,  b=2}, { a=5,  b=10}, { a=10,  b=100}]
Chrome DevToolsの[Console]パネルで結果を表示

 参照型要素のケースでも、コピー元配列への変更がコピー先配列に影響しないことが確認できます。ディープ(=深い)コピーでは、オブジェクトの参照ではなく、メンバー個々までコピーするからです。

 用途に応じて、いずれのメソッドかを使い分けるようにしてください。

処理対象:サービス カテゴリ:基本
処理対象:配列/オブジェクトコピー カテゴリ:サービス
API:angular.copy カテゴリ:ng(コアモジュール) > function(関数)

※以下では、本稿の前後を合わせて5回分(第51回~第55回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

AngularJS TIPS
51. URL経由でパラメーター情報を引き渡すには?($routeProviderプロバイダー)

ngRouteモジュールを使ったAngularJSのルーティングで、URL経由でパラメーター情報を引き渡す方法を解説する。

AngularJS TIPS
52. ルーティングの挙動/設定をカスタマイズするには?($routeProviderプロバイダー)

「テンプレートを文字列で指定(templateパラメーター)」「リダイレクト時の規則をカスタマイズ(redirectToパラメーター)」「html5モードに切り替える」という、特によく使われる3つのカスタマイズ方法を取り上げる。

AngularJS TIPS
53. 【現在、表示中】≫ 配列/オブジェクトをコピーするには?(copy)

配列のコピーで、JavaScript標準のconcatメソッドを使う場合とAngularJSのcopyメソッドを使う場合の違いを説明。シャローコピーとディープコピーとは?

AngularJS TIPS
54. コンテンツ・セキュリティ・ポリシーを利用する(ng-csp)

セキュリティフレームワーク「CSP」による制限ポリシーを有効にした場合に、AngularJSでは特定のケースでエラーとなる。そのケースの内容と回避方法を解説する。

AngularJS TIPS
55. 複数のオブジェクトを結合するには?(extend/merge)

angular.extendメソッドを利用して、既存の複数のオブジェクトを結合する方法と注意事項を解説。また、入れ子になったオブジェクトを再帰的にマージする方法も説明する。

サイトからのお知らせ

Twitterでつぶやこう!