AngularJS TIPS
配列/オブジェクトをコピーするには?(copy)
配列のコピーで、JavaScript標準のconcatメソッドを使う場合とAngularJSのcopyメソッドを使う場合の違いを説明。シャローコピーとディープコピーとは?
配列/オブジェクトをコピーするには、AngularJSのグローバルAPIであるangular.copy
メソッドを利用します。
[構文]copyメソッド
angular.copy(src [,dest])
- src: コピー元の配列/オブジェクト
- dest: コピー先の配列/オブジェクト
引数src
で指定された配列/オブジェクトは、以下のルールでコピーされます。
- 引数
dest
が省略された場合、新たな配列/オブジェクトが生成されて、戻り値として返される - 引数
dest
が明示された場合、配下の要素/プロパティを全て削除した上で、引数src
の内容をコピー - 引数
src
が配列/オブジェクトでない場合、元の値をそのまま戻り値として返す - 引数
src
/dest
が同一の配列/オブジェクトである場合、例外を発生
では、具体的な例も見てみましょう。以下は、それぞれ配列pet1
をpet2
に、オブジェクトperson1
をperson2
に、それぞれコピーする例です。
<!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>
|
コピー前:
["ニンザブロウ", "ウタ", "ハナ", "キラ"]
{name: "山田太郎", mail: "taro@example.com", age: 27, married: false}
コピー後:
["リンリン", "トクジロウ", "サチ"]
{name: "鈴木花子", mail: "hanako@example.com", age: 26}
|
1つ目のangular.copy
メソッドの引数dest
に指定された配列pet2
の末尾のキラ、および2つ目のメソッドの引数dest
に指定されたオブジェクトperson2
のmarried
プロパティは、いずれもコピー処理の後では消えている――つまり、コピーに際して、引数dest
の内容がいったん削除されていることが確認できます。
copyメソッドはディープコピー
配列/オブジェクトのコピーには、シャローコピーとディープコピーとがあります。一般的に、シャローコピーを行うにはJavaScript標準のconcat
メソッドを、ディープコピーを行うにはangular.copy
メソッドを利用します。
以下では、両者の違いを理解するために、それぞれの方法で配列をコピーしてみましょう。まずは、JavaScript標準のconcat
メソッドからです。
<!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>
|
値型:
[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}]
|
※[参照型:]の方は[Object, Object, Object]
のような表示になるので、その項目のツリーを展開することで上記のような値を確認できる。
concat
メソッドは、もともと配列を連結するためのメソッドです。ただし、引数を省略することで、現在の配列をそのまま返すこともできます。ここでは、その性質を利用して、コピーの役割を担わせているわけです。
まず、1のケースでは問題ありません。値型の要素なので、コピー元配列への変更がコピー先配列に影響することはありません。
しかし、2の参照型要素をコピーした場合、コピー元配列への変更はコピー先配列にも影響するので、これが問題となる場合があります。オブジェクトそのものではなく、オブジェクトの参照だけを浅く(=シャロー)コピーしているからです。
これを、angular.copy
メソッドで書き換えるとどうでしょう。
var dest1 = angular.copy(src1);
……中略……
var dest2 = angular.copy(src2);
|
値型:
[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}]
|
参照型要素のケースでも、コピー元配列への変更がコピー先配列に影響しないことが確認できます。ディープ(=深い)コピーでは、オブジェクトの参照ではなく、メンバー個々までコピーするからです。
用途に応じて、いずれのメソッドかを使い分けるようにしてください。
処理対象:配列/オブジェクトコピー カテゴリ:サービス
API:angular.copy カテゴリ:ng(コアモジュール) > function(関数)
※以下では、本稿の前後を合わせて5回分(第51回~第55回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
51. URL経由でパラメーター情報を引き渡すには?($routeProviderプロバイダー)
ngRouteモジュールを使ったAngularJSのルーティングで、URL経由でパラメーター情報を引き渡す方法を解説する。
52. ルーティングの挙動/設定をカスタマイズするには?($routeProviderプロバイダー)
「テンプレートを文字列で指定(templateパラメーター)」「リダイレクト時の規則をカスタマイズ(redirectToパラメーター)」「html5モードに切り替える」という、特によく使われる3つのカスタマイズ方法を取り上げる。
53. 【現在、表示中】≫ 配列/オブジェクトをコピーするには?(copy)
配列のコピーで、JavaScript標準のconcatメソッドを使う場合とAngularJSのcopyメソッドを使う場合の違いを説明。シャローコピーとディープコピーとは?
54. コンテンツ・セキュリティ・ポリシーを利用する(ng-csp)
セキュリティフレームワーク「CSP」による制限ポリシーを有効にした場合に、AngularJSでは特定のケースでエラーとなる。そのケースの内容と回避方法を解説する。
55. 複数のオブジェクトを結合するには?(extend/merge)
angular.extendメソッドを利用して、既存の複数のオブジェクトを結合する方法と注意事項を解説。また、入れ子になったオブジェクトを再帰的にマージする方法も説明する。