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

  • 38
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

これは 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の基本知識

この投稿は JavaScript Advent Calendar 201417日目の記事です。