🐴
ここではTypescriptの値渡しと参照渡しについて語られる
Typescriptと少しのC言語しか学んでいなかった私には値渡し、参照渡しという概念がピンとこなかったので、検証を通じて理解を含めることとする。
値渡し
元の引数をコピーした値が新たに変数として作成され、元の引数に影響を及ぼさない渡し方。
Typescriptや元になったJavaでは基本的にこの渡し方が仕様となる。
function addTen(x: number): void {
x += 10; // ローカル変数 x を変更
console.log(" inside:", x); // => 15
}
let num = 5;
addTen(num);
console.log("outside:", num); // => 5 (元の値は変わらない)
参照渡し
関数の引数に、変数そのものの参照を渡す方法のこと。
Typescriptでは参照を渡さないので、仕様上発生しない。
もしTypescriptが参照渡しの時、以下のような挙動になる。
type User = { name: string; age: number };
function replaceWithBob(ref u: User): void {
u = { name: "Bob", age: 30 }; // 参照先を新オブジェクトへ
}
const alice: User = { name: "Alice", age: 25 };
replaceWithBob(alice);
console.log(alice); // **(想定) { name: "Bob", age: 30 }**
参照の値渡し
呼び出し側が持つ参照(ポインタ)の コピー が引数に渡される。
値渡しの一種で参照のコピーを渡している状態。
- オブジェクト内部の変更 は同じ参照先を介して共有される。
type User = { name: string; age: number };
function birthday(u: User): void {
u.age += 1; // プロパティを直接変更
console.log(" inside:", u); // => { name: 'Alice', age: 26 }
}
const alice: User = { name: "Alice", age: 25 };
birthday(alice);
console.log("outside:", alice); // => { name: 'Alice', age: 26 }
- 再代入 は呼び出し元に影響しない。
function replaceUser(u: User): void {
u = { name: "Bob", age: 30 }; // 参照そのものを新しいオブジェクトに置き換え
console.log(" inside:", u); // => Bob
}
replaceUser(alice);
console.log("outside:", alice); // => Alice(変化なし)
意図しない変更によるバグを回避するには?
引数を直接変更するのをやめ、新しいオブジェクトを作成する。
type User = { name: string; age: number };
function birthday(u: User): User {
const newUser = { ...u }; // 新しいオブジェクトを作成
newUser.age += 1; // 新しいオブジェクトのプロパティを変更
console.log(" inside:", u); // => { name: 'Alice', age: 26 }
return newUser; // 新しいオブジェクトを返す
}
const alice: User = { name: "Alice", age: 25 };
const newAlice = birthday(alice);
console.log("outside:", alice); // => { name: 'Alice', age: 25 }
console.log("newAlice:", newAlice); // => { name: 'Alice', age: 26 }
以下のルール設定によりビルド時などにエラーが発生するため、意図しない変更を防ぐことができる
- ESLint no-param-reassign, functional/no-let など
- TypeScript --noImplicitThis, --strict
まとめ
コードの可読性や変更容易性を高く保つためにも分かりやすいコードの書き方を心がけるのが大事。
Discussion
関数の引数として変数の参照を渡す手段はありませんが、 import した変数とかは export した変数 の参照 なので export 元での代入が出来たりします。(ちゃんと他の変数への代入と違って、代入が反映されます。