結論を言えば、JavaScriptにはポインターがなく、参照の方式は私たちが知っているほかの主要なプログラミング言語とは異なります。JavaScriptでは、ある変数が別の変数を参照すること(参照渡し)には対応していません。そしてオブジェクトや配列のような複合型のものだけが「参照渡し」を使います。
この記事では以下の用語を使用します。
- スカラー(基本型):1つの値もしくはデータ型(整数、論理値、文字列など)
- 複合型:複数の値から成るもの(配列、オブジェクトなど)
- プリミティブ:ほかの場所の値を参照しているだけのもの(参照型)と区別するため、実際に値を保有しているものをプリミティブとする
JavaScriptのスカラーはプリミティブですが、Rubyなどほかの言語では参照型のスカラーになっています。JavaScriptではプリミティブなスカラーはイミュータブル(immutable)ですが、複合型データはミュータブル(mutable)です。
要点
- 変数に対するtypeof演算子(データ型を返す)が返す値で、それが「値渡し」か「参照渡し」か判別可能
- プリミティブなスカラーは数値(Number)、文字列 String, 論理値(Boolean)、未定義(undefined)、null、シンボル(Symbol)を扱う「値渡し」、複合型データは「参照渡し」
- JavaScriptでは、参照するのは中の「値」だけであり、ほかの変数やポインターの参照はしない
- JavaScriptではスカラーの値はイミュータブル、複合型データはミュータブル
「値渡し」の例
以下のコードではプリミティブなスカラー(数値型)を変数に代入したので、「値渡し」になります。変数batmanは初期化され、変数supermanには変数batmanに入っている値が渡されます。このとき、新規で値のコピーが作成され保存されます。したがって変数supermanの値が変更されても、別の変数として扱われるbatmanの値には影響しません。
var batman = 7;
var superman = batman; //assign-by-value
superman++;
console.log(batman); //7
console.log(superman); //8
「参照渡し」の例
以下のコードでは、変数に複合型データ(配列)を入れたので「参照渡し」になります。変数flashと変数quicksilverは同じものを参照しています(値の共有)。共有された値が変更されると、どちらの変数も変更後の値を参照します。そのため変数flashの値も変わっています。
var flash = [8,8,8];
var quicksilver = flash; //assign-by-reference
quicksilver.push(0);
console.log(flash); //[8,8,8,0]
console.log(quicksilver); //[8,8,8,0]
新しく参照を追加
変数に複合型データがあとから再度代入されると、新たな参照先が作られます。ほかの主要言語と違ってJavaScriptの参照形式は、変数に入れた値へのポインターであって、ほかの変数やポインターに対するポインターではありません。従って、下のコードでの変数firestormの値はそのままです。
var firestorm = [3,6,3];
var atom = firestorm; //assign-by-reference
console.log(firestorm); //[3,6,3]
console.log(atom); //[3,6,3]
atom = [9,0,9]; //value is reassigned (create new reference)
console.log(firestorm); //[3,6,3]
console.log(atom); //[9,0,9]
値を渡した場合の参照
以下のコードでは変数magnetoには複合型データ(配列)を入れたので、変数x(関数の引数)に渡す際は「参照渡し」になります。
即時関数(IIFE)内で実行したArray.prototype.pushメソッドにより、JavaScript流の参照を通じ変数magnetoの値も変更されます。しかし、そのあと変数xに対して再度配列を代入(5行目)した結果、新たな参照先が作られるため、そこから先に変数xに加えた変更は変数magnetoの値には影響しません。
var magneto = [8,4,8];
(function(x) { //IIFE
x.push(99);
console.log(x); //[8,4,8,99]
x = [1,4,1]; //reassign variable (create new reference)
x.push(88);
console.log(x); //[1,4,1,88]
})(magneto);
console.log(magneto); //[8,4,8,99]
複合型データの元の値を参照で変更
関数の引数として渡した複合型データの元の値を、参照を通じて変更する方法は、参照先である既存の複合型データの値を(前項のような参照先の変更をせずに)変更することです。以下のコードで、変数wolverineは複合型データ(配列)なので、即時関数の引数である変数xに渡した際には「参照渡し」されます。
Array.prototype.lengthプロパティの値を0にして、中身を空の配列に変えてしまいます。こうすれば、新たな参照先は作られないため変数xに入れた値は参照を通じて変数wolverineの値に反映されます。
var wolverine = [8,7,8];
(function(x) { //IIFE
x.length = 0; //make empty array object
x.push(1,4,7,2);
console.log(x); //[1,4,7,2]
})(wolverine);
console.log(wolverine); //[1,4,7,2]
複合型データを「値渡し」で保持
複合型データを「値渡し」で保持する方法は、複合型データの値のコピーを作成し、コピーした値を変数に代入する方法です。こうした場合、代入した値の参照先はコピー元の値の参照先とは異なります。従って、下のコードでは変数zoomの値は変化していません。
複合型データ(配列)の値のコピーを作る方法は、引数なしでArray.prototype.sliceメソッドを使う方法がおすすめです。
var cisco = [7,4,7];
var zoom = cisco.slice(); //create shallow copy
cisco.push(77,33);
console.log(zoom); //[7,4,7]
console.log(cisco); //[7,4,7,77,33]
プリミティブなスカラーの値を「参照渡し」で保持
プリミティブなスカラーの値を「参照渡し」で保持する方法は、スカラーの値を(オブジェクトや配列などの)複合型データのプロパティ値にすることによって複合型データでラップしてしまうことです。こうすれば「参照渡し」になります。以下のコードでは、変数speedのスカラー値が、オブジェクトflashのプロパティとしてセットされています。こうすることで、即時関数の引数である変数xに渡されるときには「参照渡し」になります。従って、下のコードでx.speedに加えた変更がflash.speedにも反映されます。
var flash = { speed: 88 };
(function (x) { //IIFE
x.speed = 55;
})(flash);
console.log(flash.speed); //55
最後に
JavaScriptの参照の仕組みを理解すれば多くの間違いを回避でき、いままで以上に良いコードが書けるはずです。
それではコーディングを楽しみましょう!
※本記事はMediumに掲載されたものです。
(原文:Quick Tip: How JavaScript References Work)
[翻訳:西尾健史/編集:Livit]