JavaScriptにおけるプリミティブ値の不変性(イミュータブル)とは何か?わかりやすく解説
JavaScriptを学んでいると、「プリミティブ値は不変(イミュータブル)であり、非プリミティブ値は可変(ミュータブル)である」という話をよく耳にします。この2つの値の型(データ型とも呼ばれます)の違いを理解しておくことは、今後の開発でつまずきを避けるためにとても重要です。
まず、プリミティブ値には次のようなものがあります。
- 数値(number)
- 文字列(string)
- 真偽値(boolean)
一方、非プリミティブ値には以下が含まれます。
- オブジェクト(object)
- 関数(function)
可変(ミュータブル)とは「変更できる」という意味です。
不変(イミュータブル)とは「変更できない」という意味です。
非プリミティブ値は可変(変更できる)
次のコードでは、人物名のリスト(配列)を変数に代入しています。
let peopleList = ['Arnold', 'Linda', 'Sylvester', 'Dolph']
ここで、このリストを変更(ミューテート)してみましょう。「変更する」とは、そのまま「書き換える」ということです。ArnoldをJean-Claudeに置き換えて、結果を出力してみます。
let peopleList = ['Arnold', 'Linda', 'Sylvester', 'Dolph']
peopleList[0] = 'Jean-Claude'
console.log(peopleList)
成功しました!既存の値に対して変更(ミューテーション)を行えたわけです。この場合、既存の値は配列(リスト)、より正確に言えば配列オブジェクトでした。
JavaScriptでは、非プリミティブ値は可変(変更可能)です。
プリミティブ値は不変(変更できない)
今度は、文字列(テキスト)の値を変数に代入してみます。
let person = 'Ernold'
おっと、残念ながら「Arnold」の最初の文字をAではなくEとタイプミスしてしまいました。すぐに修正して結果を出力してみましょう。
let person = 'Ernold'
person[0] = 'A'
console.log(person) // 出力: Ernold
えっ、なぜ「Ernold」と表示されるのでしょうか?EをAに置き換えたはずなのに!?
実は、変更しようとした対象が文字列、つまりプリミティブ値だからです。すべてのプリミティブ値は不変(変更不可)です。
可変(変更可能)なのは、非プリミティブ値(オブジェクトと関数)だけです。
混乱していませんか?
JavaScript学習の初期段階でこれに混乱するのはまったく普通のことです。表面上、上記の例は似たように見え、状況によっては同じように動作するからです。
まず、先ほどの配列から最初の名前を出力してみます。
let peopleList = ['Arnold', 'Linda', 'Sylvester', 'Dolph']
console.log(peopleList[0]) // 出力: "Arnold"
意外はないですね。「Arnold」と出力されます。
次に、先ほどの文字列(タイプミスした名前)の最初の文字を出力してみます。
let person = 'Ernold'
console.log(person[0]) // 出力: ??
どうなると思いますか?
...
...
なんと、うまくいきました!「E」と出力されます!
動作しないと思っていましたか?
どちらにせよ、ここでの重要な教訓はこうです。プリミティブ値にも、非プリミティブ値と同じような方法でアクセスすることはできますが、変更(ミューテート)することはできません。これが大きな違いです(アクセス vs 変更)。
JavaScriptにおいて、プリミティブ値は読み取り専用であり、一方で非プリミティブ値(オブジェクト/配列、関数)は読み書き両方可能です。
だからこそ、先ほどは配列の最初の要素を Arnold から Jean-Claude に変更できたのに、「Ernold」を「Arnold」に直すという単純なスペル修正はできなかったのです。文字列はプリミティブ値であり、不変だからです。
変数と値は別物
(一部の方はこう考えているでしょう)
プリミティブ値は不変だと結論づけたばかりですが、それでも私は「Ernold」のスペルミスをどうしても直したいのです。そこで、新しい方法を試してみましょう!
次のコードを見てください(注意深く読んでください)。
let person = 'Ernold'
person = 'Arnold'
console.log(person) // 出力: ??
プリミティブ値(文字列など)について学んだことを踏まえると、上記のコードは Arnold と出力されるでしょうか?それともされないでしょうか?
...
(少し考えてみてください)
...
答えは——Arnold と出力されます!
理由がわかった方もいるでしょうが、わからなくても心配いりません。もうすぐ「なるほど!」という瞬間が訪れます。次の段落を注意深く読んでください。
上記のコードが動作するのは、文字列の値を変更(ミューテート)しているのではなく、文字列自体には一切触れていないからです。代わりに、新しい文字列値 'Arnold' を person 変数に代入し、変数が参照する(指し示す)先を 'Ernold' から切り替えています。
プリミティブ値は変更できませんが、変数は変更できます!
変数は値そのものではないため、可変・不変のルールは同じようには適用されません。変数は単に値を指し示しているだけなのです。
私たちは元の文字列値 'Ernold' を直接操作したことは一度もありません。person 変数に「別の文字列 'Arnold' を指し示しなさい」と指示し、その結果を console.log() で出力しただけです。
くどいと思われるかもしれませんが、JavaScriptの仕組みについてこの理解を持つことは非常に重要です。繰り返しはあなたの味方です。
補足: このチュートリアル全体で使用してきた変数は let キーワードで宣言されたものです。let は再代入が可能(可変)です。もし const 変数を使っていたら状況は異なりますが、そのテーマは別のチュートリアルに譲ります。
まとめ
JavaScriptにおいて:
- 非プリミティブ値は、参照・アクセスでき、さらに変更(ミューテート)も可能です。
- プリミティブ値は、参照・アクセスはできますが、変更はできません。
- 変数と値は2つの別個の存在であり、それぞれ異なるルールが適用されます。
-
JavaScriptの関数式(Function Expression)とは?特徴と使い方を解説
JavaScriptにおける関数式(Function Expression)とは、関数を変数に代入して定義する方法のことです。代入された関数は、その変数名を使って後から呼び出すことができます。 関数式の主な特徴 関数を変数に格納し、変数名を指定して呼び出せる 通常の関数宣言(function declaration)と異なり、ホイスティング(巻き上げ)が行われないため、定義よりも前に呼び出すことはできない 多くの場合、名前を持たない「無名関数(匿名関数)」として定義される コールバック関数や即時実行関数(IIFE)など、さまざまな場面で活用できる この「ホイスティングされない」という点が重
-
JavaScriptのクロージャとは?仕組みと実装例をわかりやすく解説
クロージャとはJavaScriptにおけるクロージャ(Closure)とは、外側の関数の実行が完了して値を返した後であっても、内側の関数から外側の関数のスコープ(変数)にアクセスできる仕組みのことです。言い換えれば、内側の関数は、外側の関数の変数に対して常にアクセスできるということです。この性質を活用することで、データを外部から直接操作されないように隠蔽したり、関数の呼び出し間で状態を保持する処理を実現したりすることが可能になります。クロージャのサンプルコード以下は、JavaScriptでクロージャを使用した具体的なコード例です。ボタンをクリックすると、クロージャによって保持された値を使って2