MENU

JavaScript構文変数

【JavaScript】 参照渡し?値渡し?これで悩む必要がなくなる!かも

更新日:2020/12/23

 

 

関数に引数で渡すとき、値渡しか参照渡しかって意外と重要です。

 

僕はずっと、JavaScriptは次のようになってると思っていました。

 

● プリミティブは値渡し

 

● オブジェクトは参照渡し

 

ところが、少し違うようです。

 

そこで今回は、『JavaScriptの変数が値渡しか参照渡しか』ということについてお伝えします。

 

JavaScriptは共有渡し

 

結論からいうとJavaScriptは値渡しでも参照渡しでもなくて、共有渡しまたは参照渡しです。

 

大きなテキストの実体がコピーされて渡されるとか、オブジェクトそのものがコピーされるとかありません。

 

参照値が渡されます。

 

参照渡しとどこがどう違うのか、解説します。

値渡しと参照渡しのおさらい

まずは値渡しと参照渡しについて、簡単におさらいしてみます。

 

値渡しとは

 

値渡しは、変数に格納されているプリミティブやオブジェクトなどを複製します。
そして複製されたデータを、引数の値として渡します。

 

値渡し 仕組み

 

代入前と後の値は別のものなので、片方の値を変更してももう一方に影響を与えません。

 

値渡し 結果

 

参照渡しとは

 

参照渡しは、変数が格納されているアドレスを、引数に渡します。

 

参照渡し アドレス セット

 

引数に対して代入などの操作をおこなうと、アドレス上の値が変更されます。

 

クライアント

 

つまり引数に代入などの操作をおこなうと、もととなった変数の値が変更されます。

 

参照は『変数の別名』という表現があります。

 

太郎さんがTaroという名前で芸能活動をしていたとします。
芸人のTaroさんに謝礼を支払うということは、太郎さんに支払うということですね。

 

同じように変数Aの値が変わったら、変数Bも同じ値になるのです。
逆も同じことがいえます。

 

 

参照渡しできる言語例

 

php: &をつける。

 

function a( &$b ){
    $b ++;
}

 

VBA: デフォルトで参照渡し

 

Sub a( ByRef b As String){  // 明示的に指定
    b = "xxx"
}; 
Sub a2( ByVal b As String){  // 値渡し
    b = "xxx"
}; 

 

c: ポインタで参照

 

void a( int *b){
    *b = 100;
}

 

しかし、b = xxx; と代入すると参照先が変わるので、参照渡しではないという話もある。
cを使っていたのはもう20年も前のことなので、代入できるかどうかよく覚えていない。

 

c++: &で参照

 

void a( int& b ){
    b = 5;
}

 

c++はポインタ渡しもできる

JavaScriptのオブジェクトは参照渡しではない

 

JavaScriptのオブジェクトは参照渡しだと言われています。

 

ですが冷静に考えると勘違いだということがわかります。

 

let a = {
    x : 10,
    y : 20
}
function b( c ){
   c = { z: 30 };
}

 

b( a );
console.log( a ); // Object { x: 10, y: 20 } 変わっていない!

 

上の例では、関数bにオブジェクトaを渡し、新しいオブジェクトを上書きしています。

 

参照渡しだとしたら、aとcは同じ値を共有しています。
つまりcを変更したら、aも同じ値になります。

 

しかし最終的なaの値は変更されていません。

 

この現象だけ見ると、変数aが持っているオブジェクトが複製されて、関数に渡されてようにみえます。

 

つまり値渡しのように見えます。

 

JavaScriptのオブジェクトは値渡しではない

 

ではJavaScriptのオブジェクトは値渡しかというと、そうでもありません。

 

次の例は、JavaScriptのオブジェクトが参照渡しだと感じてしまう原因です。

 

 

let a = {
    x : 10,
    y : 20
}
function b( c ){
   c.x = 30 ; // (1)

 

}

 

b( a );
console.log( a ); // Object { x: 30, y: 20 }

 

引数cのメンバを変更したら、変数aの内容が変わりました。

 

引数cは変数aの参照を受け取ったんだろう、という印象を持ちますね。

 

変数が持っている値イコール参照値らしい

 

ではJavaScriptのオブジェクトは、何渡しなのか?

 

その前にJavaScriptの変数とは何なのか、簡単に解説します。

 

JavaScriptの実質的な仕様書であるECMAScript® 2019 Language Specificationに、次のような文があります。

 

The Reference type is used to explain the behaviour of such operators as delete, typeof, the assignment operators, the super keyword and other language features. For example, the left-hand operand of an assignment is expected to produce a reference.

 

A Reference is a resolved name or property binding. A Reference consists of three components, the base value component, the referenced name component, and the Boolean-valued strict reference flag. The base value component is either undefined, an Object, a Boolean, a String, a Symbol, a Number, or an Environment Record.

 

赤い文字を訳すと、次のようになります。

 

『割り当ての左側のオペランドは参照を生成することが期待されています。』

 

代入の『=』を割り当て演算子と呼ぶことがあります。

 

次のように書くと、右側の結果を左側に割り当てているわけです。

 

左側 = 右側

 

 

左側は代入される側の変数ですね。

 

つまり意訳すると『変数は参照です』と書いてあるわけです。

 

値取得は参照先。代入は参照上書き

 

変数が持っている値は、オブジェクトやプリミティブへの参照値です。

 

この記事では参照値として100000とアドレスかのように書いてありますが、実際には実データの位置をあらわすラベルやインデックス的なものです。(ECMAScriptの仕様には明記されていませんので、どのような値にするかはブラウザなどに組み込む実装者の判断によります)

 

変数から変数へ代入をおこなうと、参照値がコピーされます。

 

変数から変数へ代入

 

結果的に両方の変数が同じ値となります。

 

その後に変数に他の値を代入すると、値への参照値が上書きされます。

 

変数へ代入

 

 

計算などの対象に変数を指定すると、変数が持っている参照値からオブジェクトやプリミティブにアクセスし、値を取得します。

 

参照先のオブジェクトやプリミティブから値が取得

 

 

取得した値を元に計算した結果は、新しい領域が確保されて、そこに格納されます。

 

格納したデータにも参照値が設定されて、設定された参照値が変数にセットされます。

 

変数に代入するときは、結果が生成され、その結果の参照がセット

 

このとき既存の変数を代入先として指定すると、その変数の参照値が上書きされるのです。

 

またこの流れからわかるように、新しいデータは別途メモリ上に確保され、元データは上書きされません。

 

参照によるデータ変更をしたくてもできない仕組みになっているのです。

引数で指定したときの挙動

 

上のことをふまえて、引数で渡したときの挙動を考えてみます。

 

次のようなコードがあるとします。

 

let a = 1;
function b( c ){
            c =  10 ;
}
b( a );

 

変数aには、値1が格納された領域への参照値がセットされています。

 

変数a

 

関数bを呼び出すと、引数cに変数aが持つ参照値が渡されます。

 

引数cに変数aが持つ参照値が渡されが渡される

 

引数cは、渡された参照値から、値1を得ることができます。

 

次に引数cに10をセットします。

 

引数c新しい参照がセットされる

 

新しい領域に値10が用意され、参照値が変数cにセットされます。

 

この時変数aが持つ参照値は変更されていません。

 

そのため、関数終了後も値1として使用できます。

 

このような引数の渡し方を、「共有渡し」とか「参照値渡し」というようです。

オブジェクトも共有渡し

 

引数で渡したオブジェクトのメンバを変更すると、引き渡し元のメンバも変更されてしまいます。

 

プリミティブとオブジェクトでは渡し方に違いがあるように感じますが、実際は同じです。

 

オブジェクトも共有渡しです。

 

オブジェクトも共有渡し

 

上の図をみると、参照値は同じオブジェクトの実体を指しています。

 

そのため、変数A.xと引数B.xは同じ値を参照します。

 

この状態での引数B.xの値の変更は、変数A.xの変更と同じ意味を持ちます。

 

プロパティ値を変更

 

 

 

しかし引数そのものに異なるオブジェクトを代入すると、参照する実体が変更されます。

 

引数に異なるオブジェクトを代入すると、参照する実体が変更される

 

大もとの変数Aは、参照値が変わっていないので以前と同じ値を使用します。

まとめ

 

JavaScriptの内部では、プリミティブかオブジェクトかを判断して渡し方を変える。
そんな面倒なことはやっていませんでした。

 

とても単純な渡し方をしていたのです。

 

参考記事:
【JavaScript】 ECMAScript2020から変数宣言を読み解く

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ