JavaScriptのイミュータブルの理解を棚上げしていた落書き人
JavaScript言語としてのミュータブル、イミュータブルの解説を何度読んでも理解に苦しんでいました。
・イミュータブルな型は不変なのに再代入できるじゃん?
・ミュータブルなオブジェクト型の可変と何?
たぶん、文献への理解が浅いのだと思いますが、深く読もうとすればするほど・・・、長らくこの疑問から逃げていました。
そんな落書き人を、このドツボから解き放ってくれるヒント出会いました。
https://qiita.com/makotoo2/items/fc3a617882916f9775f5
そしてJavaScriptの変数名やオブジェクトのプロパティ名やメソッド名の挙動を、C言語のポインターとして考えてみたら、腑に落ちました。
なおJavaScriptエンジンがC言語のポインター的な処理を行っているのかはわかりまん。あくまでも落書き人がこれで納得できたという例え話です。
落書き人の帰結:JavaScriptのimmutableと代入・再代入の考え方
落書き人が理解できていなかった留意点
・イミュータブルなのは値が置かれた領域の内容
・変数名は値が置かれた領域を指し示すポインター
・変数への再代入は値へのポインターの差し替え
・値渡しも変数名がないテンポラリーっぽいポインター
・どの様なポインター参照からも紐付けが無くなった値の領域は解放対象
・オブジェクト型は変数の階層表現の集合
JavaScriptでのプログラミングでっシンタックスとしてのimmutableと再代入を意識しなければならない場面
・文字列をブラケット記法「str[n]=値」の代入モドキは無視される
・再代入を許さない変数名は変数宣言constで定義する
・再代入を許さないオブジェクト型のプロパティ名は「Object.freeze()」で再代入は無視される
・変数名2=変数名1はポインターの複製である
├ 変数名が指し示す値の実態が複製されるわけではない
└ 値がオブジェクト型の場合はこの意識が重要
上記の考えに至った考察メモ
1.変数名は実態を参照するポインターなのかを考察
プリミティブ型とオブジェクト型
JavaScriptの変数名とオブジェクトのプロパティ名は、すべてその値の実態が収められ領域を参照(リファレンス)するポインター。
指し示される値の実態にはプリミティブ型とオブジェクト型(広義な意味)の2つ。
・プリミティブ型は値そのもの
・オブジェクト型の値実態への参照するポインターの集合
var n1 = 1; //数値型の変数名に初期値1を代入
var a1 = [1, 3, 4]; //array型の変数名に初期値arrayオブジェクトを代入
オブジェクト型の実態
変数 a1 が示す a1arrayオブジェクトの図は複雑なので、開発ツールのWatchに表示される内容で考察
・a1:0 は配列の1番目の値が収まっている領域へのポインター
・a1:1 は配列の1番目の値が収まっている領域へのポインター
・a1:2 は配列の1番目の値が収まっている領域へのポインター
・length は配列要素の数が収まっている領域へのポインター
・prototye(*1) はarrayオブジェクトの各メソッドが収まっている領域へのポインター
*1 prototypeの意味は関数のプロトタイプの文献を参照されたし
2.プリミティブ型のイミュータブルとは何なのかを考察
プリミティブ型のイミュータブルで値が不変と言う意味は、値が収められている領域が不変という意味で捉えると腑に落ちます。
ではイミュータブルな型の値が格納されている領域と紐付けられている変数名に、別の値を代入すると、どの様な動作が行われるかを推測してみた。
var a = 1;
var b = a; // (1)
a = a + 2; // (2)
b = 4; // (3)
(1) 変数aをと変数bに代入
変数bのポインターも「1」という値が格納されている同じ領域を指し示す。

(2) 変数aに「3」を再代入
変数aのポインターは新たに「3」という値が格納された領域を指し示す。

(3) 変数bに「4」を再代入
変数bのポインターは4が格納された領域を指し示す。
1が格納された領域を指し示すポインター変数が無くなりガベージコレクションの対象になる。

3.オブジェクト型のミュータブルとは何なのかを考察
オブジェクト型がミュータブル?
オブジェクト型は可変・・・、正解であって不正解ともいえる。
オブジェクト型のプロパティ名が指し示すポインターの入れ替え(再代入)が可能という意味です。
そのポインターが指し示す値の実態はイミュータブルです。
日付オブジェクトで確認
var theDate1 = new Date();
var theDate2 = theDate1; //日付オブジェクトの実態を指し示すポインターが複製される
theDate1.setFullYear(1995); //複製した変数が指し示す日付オブジェクトの年を変更する
console.log(theDate1); // 年が1955に変わっている
console.log(theDate2); // theDate1とtheDate2いうポインター変数が指し示す領域が同じなので年は1955になっている
Arrayオブジェクトなどのオブジェクト型を変数に代入した場合も考え方は同じです。
var a1 = [1, 3, 4];
var a2 = a1; //(1)
a2[1] = 9; //(2)
console.log(a1); //配列オブジェクトで[1,2,3]
console.log(a2); //数値型で9
a1 = [4,5,6]; //(3)
console.log(a1); //配列オブジェクトで[4,5,6]
console.log(a2);
4.関数の引数はポインターの複製
引数は変数名の複製
function func1(a1) {
console.log(`Begin of Func1 , value is a1:${JSON.stringify(a1)}`); // (1)
a1.p1 = 2;
console.log(`End of Func1 , value is a1:${JSON.stringify(a1)}`); // (2)
return;
}
function func2(a2) {
console.log(`Begin of Func2 , value is a2:${JSON.stringify(a2)}`); // (3)
a2 = {
p1: 3
}
console.log(`End of Func2 , value is a2:${JSON.stringify(a2)}`); // (4)
return;
}
const o1 = {
p1: 1
}
func1(o1);
console.log(`When the func1 returns , value is o1: ${JSON.stringify(o1)}`); // (2)'
func2(o1);
console.log(`When the func2 returns , value is o1: ${JSON.stringify(o1)}`); //(4)'
(1) func1に入った時点
変数名o1ポインターはオブジェクト型o1の実態を指し示す
オブジェクト型o1のプロパティ名p1ポインターは値1の実態を指し示す
引数a1ポインターは変数名o1ポインターの複製
(2) func1を終了する直前
オブジェクト型a1のプロパティ名p1ポインターは値2の実態を指し示す
(2)' func1から戻った時点
オブジェクト型o1のプロパティ名p1ポインターは値2の実態を指し示す
引数a1はスコープから外れたので見えない
(3) func2に入った時点
変数名o1ポインターはオブジェクト型o1の実態を指し示す
オブジェクト型o1のプロパティ名p1ポインターは値2の実態を指し示す
引数a2ポインターは変数名o1ポインターの複製
(4) func2を終了する直前
オブジェクト型a2ポインターがオブジェクト型a2への実態を指し示す
(4)' func2から戻った時点
オブジェクト型o1ポインターが示す、オブジェクト型o1のプロパティ名p1ポインターは値2は変わらず
引数a2はスコープから外れたので見えない