メインコンテンツまでスキップ

値渡しと参照渡し

関数の実引数に変数を指定するとき、仮引数への渡し方に値渡し(pass-by-value)と参照渡し(pass-by-reference)の 2 種類があります(値渡しは更に細かく分けられます)。

  • 実引数:関数を呼び出す側が引き渡す即値(123 など)か変数
  • 仮引数:関数側が引き受ける変数

参照渡しできないプログラミング言語もあります。

  • 参照渡しできる言語:C++, C#, VB.NET, PHP, Swift など
  • 参照渡しできない言語:C, Java, JavaScript, Python, Ruby など

変数の種類

変数には値型と参照型の 2 種類があります。

  • 値型変数:変数に代入値が保持される(保持値=代入値)
  • 参照型変数:変数に参照値が保持され、変数から代入値を参照する(保持値 ≠ 代入値)

参照値は、代入値がメモリ上のどこにあるのかを識別する値で、メモリアドレスなどが用いられます。
値型変数も参照型変数も値渡しと参照渡しができます。

値渡し

値渡しは、変数の値(保持値)が渡されます。

  • 値型変数:代入値のコピーが渡される(コピー渡し)
  • 参照型変数:参照値のコピーが渡され、参照先の値はコピーされずに共有される(参照の値渡し、共有渡し、値共有)

実引数と仮引数は、独立した変数、独立した値になります。そのため、関数内で仮引数に値を代入しても、関数呼び出し元に影響しません。
次のコードは、C++言語の値渡しの例です。変数a1で初期化され、関数changeに変数aの値が渡されます。関数change内で仮引数n2を代入しても、呼び出し元の変数a1のままです。

cpp
#include <print>
void change(int n) { // 仮引数nは値型変数の値渡し
n = 2; // 仮引数に代入しても呼出し元に影響しない
}
int main() {
int a = 1; // 値型変数
change(a); // 実引数は値型変数a
std::println("{}", a); //=> 1
return 0;
}
cpp
#include <print>
void change(int n) { // 仮引数nは値型変数の値渡し
n = 2; // 仮引数に代入しても呼出し元に影響しない
}
int main() {
int a = 1; // 値型変数
change(a); // 実引数は値型変数a
std::println("{}", a); //=> 1
return 0;
}

参照渡し

参照渡しは、変数自体が渡されます。呼び出し元の変数を参照するようにして変数が共有されます(変数共有)。
実引数と仮引数は、同じ変数、同じ値になります。そのため、関数内で仮引数に値を代入すると、関数呼び出し元に影響します。
次のコードは、C++言語の参照渡しの例です。 変数a1で初期化され、関数changeに変数a自体が渡されます(呼び出し元の変数を参照・共有します)。関数change内で引数に2を代入すると、呼び出し元の変数aの値も2になります。

cpp
#include <print>
void change(int &n) { // 仮引数nは値型変数の参照渡し
n = 2; // 仮引数に代入すると呼出し元に影響する
}
int main() {
int a = 1; // 値型変数
change(a); // 実引数は値型変数a
std::println("{}", a); //=> 2
return 0;
}
cpp
#include <print>
void change(int &n) { // 仮引数nは値型変数の参照渡し
n = 2; // 仮引数に代入すると呼出し元に影響する
}
int main() {
int a = 1; // 値型変数
change(a); // 実引数は値型変数a
std::println("{}", a); //=> 2
return 0;
}

JavaScript は値渡し

JavaScript には C++のような参照渡しをする機能はありません。関数の引数はすべて値渡しです。したがって、仮引数に値を代入しても関数呼び出し元には影響しません。

js
function change(n) {
n = 2;
}
let n = 1;
change(n);
console.log(n);
1
js
function change(n) {
n = 2;
}
let n = 1;
change(n);
console.log(n);
1

ところが、オブジェクトについては少し特殊です。オブジェクトは参照型で変数はオブジェクトを参照します。
その変数を別の変数に代入したとき、オブジェクトがコピーされて新たなオブジェクトが作られるのではなく、異なる変数から同じオブジェクトを参照します。
たとえば、次のコードのようにオブジェクト{ n: 1 }を変数xに代入し、さらに変数xを変数yに代入すると、変数xと変数yは同じオブジェクトを参照します。変数yのプロパティnを変更すると、変数xのプロパティnも変更されます。

js
const x = { n: 1 };
const y = x;
y.n = 2;
console.log(x);
{ n: 2 }
js
const x = { n: 1 };
const y = x;
y.n = 2;
console.log(x);
{ n: 2 }

ただし、変数yに別の値を代入した場合は、変数xと変数yは同じオブジェクトを参照しなくなり、変数yのプロパティの変更は変数xには影響しなくなります。

js
const x = { n: 1 };
let y = x;
y = { n: 2 }; // yに別オブジェクトを再代入
y.n = 3;
console.log(x);
{ n: 1 }
js
const x = { n: 1 };
let y = x;
y = { n: 2 }; // yに別オブジェクトを再代入
y.n = 3;
console.log(x);
{ n: 1 }

以上のように JavaScript では、オブジェクトが代入されてる変数を別の変数に代入したとき、参照先のオブジェクトを共有するようになっています。
共有されたオブジェクトのプロパティを変更すると他の変数にも影響します。この仕様は関数の引数も同様です。
たとえば、次のコードのようにオブジェクト{ n: 1 }を変数xに代入し、さらに変数xを関数changeの引数yに渡すと、変数xと変数yは同じオブジェクトを参照します。変数yのプロパティを変更すると、その影響は関数呼び出し元の変数xのプロパティも変更されます。

js
function change(y) {
y.n = 2;
}
const x = { n: 1 };
change(x);
console.log(x);
{ n: 2 }
js
function change(y) {
y.n = 2;
}
const x = { n: 1 };
change(x);
console.log(x);
{ n: 2 }

引数の場合も、変数の再代入の仕様と同様に、変数yに別の値を代入した場合は、変数xと変数yは同じオブジェクトを参照しなくなるため、変数yへの変更は変数xに影響しなくなります。

js
function change(y) {
y = { n: 2 };
y.n = 3;
}
const x = { n: 1 };
change(x);
console.log(x);
{ n: 1 }
js
function change(y) {
y = { n: 2 };
y.n = 3;
}
const x = { n: 1 };
change(x);
console.log(x);
{ n: 1 }

以上をまとめると、JavaScript の関数の引数は値渡しです。注意点として、オブジェクトは変数の場合と同様に、引数と呼び出し元の変数は同じオブジェクトを共有する仕様です。そして、もし引数のオブジェクトのプロパティを変更した場合、その変更は関数呼び出し元に影響します。

学びをシェアする

・JavaScript の引数はすべて値渡し
・JavaScript には C++言語の参照渡しと同等の仕組みはない
・ただし、オブジェクトは値を共有する
・共有したオブジェクトを関数で変更した場合、呼び出し元にも影響する
・オブジェクトの共有は引数に限ったことではない

『サバイバルTypeScript』より

この内容をXにポストする