Edited at

[初学者向け] コアJavaScriptにおける変数の参照について確認してみる

More than 3 years have passed since last update.

これは JavaScript Advent Calendar 2014 の17日目(12/17)の投稿です。初学者向けの内容です。(ていうか自分が初学者です..)

自分が JavaScript をやり始めたとき、


  • 変数に何がはいってるのか、変数の受け渡しでやりとりされるのは何なのか

  • 関数の return で返ってくるものが何なのか

が良く分からず困惑したので、その時のことを思い出しながら挙動を再確認してみました。


1. 変数へ値を代入した場合の挙動

JavaScript では、変数に型はありませんが、値には 基本型オブジェクト型 の2種類があります。


A. 基本型

値そのもの が渡されます。


① 値として「数値」を代入した場合

代入しているのは2行目です。


検証用コード

var num1 = 999;

var num2 = num1; // num2 には値そのものが渡されている

num1 = 0; // num1 の値を後から変更

console.log(num1); // 0 が出力される
console.log(num2); // 999 が出力される(num1 の変更の影響を受けていない)



② 値として「文字列」を代入した場合

代入しているのは2行目です。


検証用コード

var str1 = 'あああ';

var str2 = str1; // str2 には値そのものが渡されている

str1 = 'いいい'; // str1 の値を後から変更

console.log(str1); // いいい が出力される
console.log(str2); // あああ が出力される(str1 の変更の影響を受けていない)



③ 値として「論理値」を代入した場合

代入しているのは2行目です。


検証用コード

var flag1 = true;

var flag2 = flag1; // flag2 には値そのものが渡されている

flag1 = false; // flag1 の値を後から変更

console.log(flag1); // false が出力される
console.log(flag2); // true が出力される(flag1 の変更の影響を受けていない)



基本型には上記以外もありますが割愛。



B. オブジェクト型

アドレス(参照) が渡されます。


① 値として「配列」を代入した場合

代入しているのは2行目です。


検証用コード

var arr1 = [1, 1, 1];

var arr2 = arr1; // arr2 にはアドレスが渡されている

arr1[0] = 99; // arr1 の中身を後から変更

console.log(arr1); // [99, 1, 1] が出力される
console.log(arr2); // こちらも [99, 1, 1] が出力される(arr1 の変更の影響を受けている)



② 値として「オブジェクト(key-value)」を代入した場合

代入しているのは2行目です。


検証用コード

var obj1 = {name:'山田', age: 32};

var obj2 = obj1; // obj2 にはアドレスが渡されている

obj1.name = '高橋'; // obj1 の中身を後から変更

console.log(obj1); // {name: "高橋", age: 32} が出力される
console.log(obj2); // こちらも {name: "高橋", age: 32} が出力される(obj1 の変更の影響を受けている)



③ 値として「関数」を代入した場合

代入しているのは4行目です。


検証用コード

var func1 = function(){}; // 空の関数を定義

func1.num = 999; // func1 のプロパティ num に値をセット

var func2 = func1; // func2 にはアドレスが渡されている

func1.num = 0; // func1 の中身を後から変更

console.log(func1.num); // 0 が出力される
console.log(func2.num); // こちらも 0 が出力される(func1 の変更の影響を受けている)



オブジェクト型には上記以外もありますが割愛。



2. 変数の再定義

上記の例 1-B-①②③ では arr1[0] = ○○ obj1.name = ○○ func1.num = ○○ という風に 中身の要素にアクセスして 値を変更していました。この場合は、変数 arr obj1 func1 に格納されている アドレス(参照) には何の影響もありません。

ですが、もし arr1 = ○○ obj1 = ○○ func1 = ○○ とすると、それは単に 変数の再定義 なので アドレス(参照)が新しいものへ上書き されます。検証用コードを下記に示します。


① 配列


検証用コード

var arr1 = [1, 1, 1];

var arr2 = arr1; // arr2 にはアドレスが渡されている

arr1 = []; // arr1 に [] へのアドレスが上書きで格納される

console.log(arr1); // [] が出力される
console.log(arr2); // [1, 1, 1] が出力される(arr2 に格納されていたアドレスは維持されている)



② オブジェクト(key-value)


検証用コード

var obj1 = {name:'山田', age: 32};

var obj2 = obj1; // obj2 にはアドレスが渡されている

obj1 = {}; // obj1 に {} へのアドレスが上書きで格納される

console.log(obj1); // {} が出力される
console.log(obj2); // {name: "山田", age: 32} が出力される(obj2 に格納されていたアドレスは維持されている)



③ 関数


検証用コード

var func1 = function(){};

func1.num = 999;

var func2 = func1; // func2 にはアドレスが渡されている

func1 = function(){}; // func1 に function(){} へのアドレスが上書きで格納される

console.log(func1.num); // undefined:未定義エラー(numというプロパティは存在しない)
console.log(func2.num); // 999 が出力される(func2 に格納されていたアドレスは維持されている)


こう見ると、JavaScript で変数 hogehoge = ○○ と値を代入する際は 基本型とオブジェクト型で、挙動の差異が無い ということになります。


3. 関数の return 値を受け取った際の挙動


ここでは 配列 の return についてのみ述べますが オブジェクト(key-value)関数 の return についても同じ話です。


配列を return する関数 rtnArray を定義し、その関数を用いて arr1 arr2 という2つの配列を生成してみます。

// 配列を retrun する関数を定義

function rtnArray(){
return [1, 1, 1];
}

// 先ほどの関数を用いて配列を生成
var arr1 = rtnArray();
var arr2 = rtnArray();

arr1[0] = 99; // 1つ目の配列の中身を変更

console.log(arr1); // [99, 1, 1] が出力される
console.log(arr2); // [1, 1, 1] が出力される(つまり、arr1 と arr2 は独立している)

つまり、6行目と7行目で次のように書いてある処理は、

arr1 = rtnArray(); 

arr2 = rtnArray();

次のように書くのと同義、ということになります。

arr1 = [1, 1, 1];

arr2 = [1, 1, 1];

つまり 関数の ruturn を受け取る 変数の再定義 がされています。

ここまで理解すると、次のようなコードで、同じ count() という関数を呼び出していても、conter1counter2 では 別々のメモリ領域にあるローカル変数 localNum を参照している ということが分かります。

// 関数(function)を return する関数を定義

var count = function(initNum) {
var localNum = initNum;
return function() {
return localNum++;
};
};

// 初期値を 1 として カウンターを作成
var counter1 = count(1);
console.log(counter1()); // 1 が出力される
console.log(counter1()); // 2 が出力される
console.log(counter1()); // 3 が出力される

// 初期値を 9 として カウンターを作成
var counter2 = count(9);
console.log(counter2()); // 9 が出力される
console.log(counter2()); // 10 が出力される
console.log(counter2()); // 11 が出力される

console.log(counter1()); // 4 が出力される(2つの独立したカウンターが実現できている)

ちなみに上記の localNum は関数(メソッド)経由でしかアクセスできません。 関数閉包(クロージャ) と呼ばれるものです。


おわりに

JavaScript では 変数が何をさしているのか を頭にいれておかないと、既存のコードを読む際に混乱するので、このあたりは意識していきたいところです。(自戒)

あまり役に立たない記事ですみません.. よろしければこちらもどうぞ。

サーバサイドエンジニアでも押えておきたい!コアJavaScriptの基本知識