こんにちは、「ふ」です。
今回はJavaScriptにおける、「値渡し」「参照渡し」について説明します。
const a = 1; const b = 1; console.log(a === b) 「「3▶︎ true」」
定数aとbを作り、どちらも値を「1」としました。
そのあとaとbを比較。これは当然「true」ですよね。
const c = [1,2,3]; const d = [1,2,3]; console.log(c === d); 「「3▶︎ false」」
次に定数cとd。値はまったく同じ、1〜3の数値をならべた配列です。
そして2つを比較してみると....何と「false」となってしまいます💧
この謎の現象はなんなのか?
理由は、定数aやbには値そのものを渡す「値渡し」がされているのに対し、cやdには値への参照を渡す「参照渡し」がされているためです。
この2種類の渡し方についての理解がないと、せっかく作ったプログラムが意図せぬ結果を招くケースも出てきます。
この記事では変数や定数の「値が記録される仕組み」の部分から、参照渡し/値渡しについてお話ししていきます。ちょっと普段より深めの内容となりますが、根っこの部分から学んだほうがむしろ習得はスムーズに進むので、そうさせていただきました。
じっくり理解を深めておきましょう。
「値渡し」「参照渡し」をちゃんと理解するには、「JavaScriptの値」と「ブラウザのメモリ(イメージ)」について知っておく必要があります。
「「1//プリミティブ型の値」」 const str = "hello"; let num = 10; let judge = false;
JavaScriptの値は大きく2つに分類することができます。
文字列や数値、これらは単一のデータを所持しているもので、「プリミティブ型」などと呼ばれています。primitiveは「原始的な、単純な」という意味で、trueやfalseの真偽値もこの「プリミティブ型」に含まれます。
このプリミティブ型の値を扱った場合、「値渡し」が採用されます。
「「1//複合型の値」」 const arr = ["a","b","c"]; const obj = { d:"DDD", e:"EEE" };
一方で配列やオブジェクトなどの値は、いくつかのデータがまとめれたものを1つの「値」として扱うものです。こちらは「オブジェクト型」と呼ばれるもので、関数もこのオブジェクト型に分類されます。
オブジェクト型の値をやりとりする際には「参照渡し」となります。
JavaScriptのコードを読み込んで反映させるブラウザには、変数/定数や関数を記憶しておくメモリ領域が用意されています。
メモリの処理についてはそれぞれのブラウザによる内部処理であるため、その仕組みや動きを目で確認することはできません。
そのためここでは、ブラウザのメモリ領域をグレーの部分⬆︎でイメージすることにしました。
変数や定数に対して代入などの処理を行った場合、ブラウザのメモリは必要な領域を確保し、その内容を保存していきます。
もしこの記憶装置がなかったら。設定しておいた値や関数を呼び出すことはもちろんできません〜というかJavaScript自体動きません。
これらを踏まえた上で、「値渡し」「参照渡し」の仕組みを見ていきましょう。
「「1//プリミティブ型の値」」 const str = "hello"; let num = 10; let judge = false;
プリミティブ型の値のやりとりは、「値渡し」方式が使われます。
変数を使ってその様子を見てみましょう。
先づは変数「a」を宣言し、値を入れてみました。
let a = 5;
値を代入すると、ブラウザのメモリ上に値を記録しておくための領域が確保され、そこに内容が保存されます。
そして後からここアクセスするためには、この領域に名前なりidなり、何らかの識別子をつけておく必要があります。
そこで宣言した変数名「a」が識別子として使用されます。
aの値を変更してみましょう。
a = 10;
そうしたところ、「記録領域a」の内容が上書きされました。
もう1つ変数「b」を作ってみます。
let b = 10;
このとき、ブラウザのメモリ上では、
@1@メモリ上に新たに記録領域を作成⬇︎
@2@そこに値「10」を保存⬇︎
@3@作成された領域の識別子として変数名「b」が使用される
〜という流れで処理が行われます。
現在aとbには、同じ数値の「10」が入っています。これを確かめてみましょう。
console.log(a === b); 「「3▶︎ true」」
「変数aとbは同じ」と出力されました。
これをもっとちゃんと言うと、「領域aの内容と領域bの内容は等しい」ということです。
さらに変数cを作ってbを代入してみましょう。
let c = b;
この場合。
@1@bという記憶領域が複製される⬇︎
@2@複製された領域に、識別子「c」が使用される
〜という流れになります。決して、元々あった記憶領域bの識別子が変更されるわけではありません。
このように「値渡し」とは、変数/定数に記憶領域そのものが渡されている状態を言います。1つの変数/定数に対して必ず1つの記憶領域が存在しているというルールになっているのです。
またこのルールに基づき、先ほどのように「let c = b」とすると、新たに記憶領域が確保される、ということも覚えておいてください。
次は「参照渡し」の動きを見ていきましょう。
オブジェクトや配列など、「オブジェクト型の値」のやりとりには、参照渡しが使われます。
const A = { m:10, n:20 };
試しにオブジェクト「A」を作ってみました。
するとブラウザのメモリ上には、内容を記憶させるための領域が割り当てられます。
ここまでは値渡しと同じです。
ただ今回は、「定数A」がこの記憶領域の識別子となるわけではありません。
定数Aには、この領域への参照、つまりリンクが与えられるのです。
先ほどの値渡しの場合、変数/定数は記憶領域そのものでした。一方参照渡しの場合、変数/定数は記憶領域へのリンクとなります。
では「参照渡しの場合、記憶領域自体に何かしらの識別要素が必要なのでは....その辺どうなのか?」となるかもしれません。
実際には、領域自体にもidなどの識別子が付けられているのでしょう。が、これまた各ブラウザの内部的な処理であるため、こちらから確認することはできません。
領域のid(か何か)が確認できないのは、JavaScriptのちょっと残念なところでもあります。
それではわかりやすいよう、今作った記憶領域を「x」としましょう。
const B = { m:10, n:20 };
そして、もう1つオブジェクト「B」を作ります。
内容はオブジェクトAとまったく同じです。
オブジェクトをリテラル形式で初期化しました。するとメモリ上には新たな記憶領域が確保されて内容が保存されます。この記憶領域を「y」とします。
今回も保存された値は「オブジェクト型」なので、定数Bにはそのリンクが渡されます。
扨(さて)、AとBのリンク先に保存されている内容は、全く同じものです。
「定数AとBは同一なのか?」評価してみましょう。
console.log(A === B); 「「3▶︎ false」」
なんと ! 「false」と出ました。
参照渡しでAとBに渡されているのは、「記憶領域へのリンク」です。たとえ保存内容が同じであっても、それぞれ別の記憶領域に対してのリンクを持っているため、評価は「真」とはなりません。
「値渡し」の場合では、変数/定数が記憶領域そのものであったため、保存内容である「値」が評価されました。
が、「参照渡し」においては、「どの記憶領域へのリンクを持っているか」ということが評価されるのです。領域の保存内容は関係ありません。
冒頭で紹介した謎の現象は、参照渡しと値渡しでは「評価される対象が違っていた」からなんですね。
今度は新たに定数Cを宣言し、すでに作ってある定数Aを渡してみました。
const C = A;
すると、値渡しのときのように記憶領域は複製されません。
定数Aの持っている「記憶領域Aへのリンク」が複製され、定数Cに渡されます。
複製されるのはただの「リンク」です。
1つの記憶領域に対してリンクを共有するのは、特に問題ありません。HTMLで言えば、複数の要素が同じhref属性を持っているだけのことです。
ではAとCが等しいとされるか、出力してみます。
console.log(A === C); 「「3▶︎ true」」
「true」となりました。
参照が渡されている定数/変数同士の比較は、記憶領域の内容ではなく「リンクの内容」が評価されます。当然、AとCは「等しい」とされます。
const d = 5; d = 6; 「「4▶︎ error」」
⬆︎はconstで初期化した定数「d」に対し、2行目で別の値を代入したものです。
定数なのに値を変更しようとしたのでerrorとなりました。
が、これは「値渡しされた定数」の場合です。参照渡しの定数は、値を変更することができてしまいます。
先ほどの定数Aの値に対し、変更を試みます。プロパティmの値は「10」ですが、これを「100」としてみましょう。
A.m = 100; 「「3▶︎ OK」」
これはエラーになりません。
定数Aに入っているのは「リンク」なので、リンク自体に変更がなければ、参照先の内容は変更することができます。
それでは強引にリンク内容を変更してみましょう。
さっき作った定数Bを渡してみます。
A = B; 「「4▶︎ error」」
やはりエラーとなってしまいました。このことからも、定数Aに渡されているのは「リンク」であることが確認できます。
扨(さて)定数Aのリンク先となる、記憶領域の内容が変更されました。
このとき同じリンクを持つ定数Cはどうなっているのでしょうか。
console.log(C); 「「3▶︎ {m:100,n:20}」」
定数Cについても、プロパティの内容が変更されています。
AとCには同じリンクが入っているのでリンク先の内容を変更した場合、当然反映されることになります。
let i = 5; let j = i; 「「3// iの内容を変更」」 i = 10; console.log(i) 「「3▶︎ 10」」 console.log(j) 「「3▶︎ 5」」
ちなみに値渡しの動きもみておきましょう。
変数「i」を初期化し、そのあと宣言した変数「j」に「i」を渡しています。
そして「i」の値を変更したのですが、双方を出力したところ変更されているのは「i」だけです。
「値渡し」で変数を渡したときは記憶領域自体が複製され、「i」も「j」も独立した領域を所持するようになります。そのため一方を書き換えたところで、もう一方に影響を及ぼすことはありません。
ここまでみてきたように、「参照渡し」とは、変数/定数に記憶領域へのリンクが渡されている 状態のことです。
変数や定数をconsole.logしたとき、参照/値渡しにかかわらず「記憶領域の内容」が出力されてしまいます。なので見分けがつきにくいのですが、このように値をやりとりすることで、双方の違いが検証できました。
今回はJavaScriptの「参照渡し」「値渡し」について解説してきました。最後までお読みくださり、ありがとうございます。
それにしても「参照渡し」という仕組み、なぜわざわざこんなものを用意しているのか?1番のメリットは、メモリの消費を節約するところにあります。
JavaScript上で何らかの処理が行われる度、ブラウザのメモリに領域が割り当てられ、そのことが記録されていきます。
ここで単一データである「プリミティブ型」の値であれば、1つ2つ追加したところでブラウザに大きな負荷をかけることはないでしょう。
しかし時には大量のデータが詰め込まれている「複合型」の値の場合。
変数/定数同士でやりとりするたびにその大きな記憶領域を複製していったとしたら、ブラウザのメモリは圧迫されてしまいます。それはパフォーマンスの低下に直結することになります。
そこで採用されたのが「リンク」だけを渡す「参照渡し」という方法です。(内部処理なので正体は不明ですが)リンク1行だけを複製するのであれば、ブラウザに大きな負担をかけることもありません。
JavaScriptを学習する側にとっては少しばかり厄介な概念ですが、「参照渡し」はパフォーマンスを円滑にするための大切な仕組みです。ご自身でもコードを試して、その動きをしっかりと理解しておきましょう。
ではまた〜 ♬
値型と複合型データのイメージ。めもり領域を大幅に消費してしまう。 じゃあidかなんかを与えるだけにすればメモリも節約。 やはりメモリ領域の確保が大変。
いま、プリミティブ型の値が代入されました。
JavaScriptの矢印「=>」〜これはアロー関数というものです。
2022.01.09
使い方とメリットについて解説。
JavaScriptのforEachとは? 〜配列のループメソッドについて。
2022.03.24
HTML要素の一括編集にも。
JavaScriptのドルマーク$に中括弧{ }、テンプレートリテラルについて。
2022.02.09
クオテーション祭り、さようなら。
swift、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。