40
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-17

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

40
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?