|
| 1 | +--- |
| 2 | +title: 定数とオブジェクトの参照 |
| 3 | +--- |
| 4 | + |
| 5 | +import CodeBlock from '@theme/CodeBlock'; |
| 6 | +import Term from "@site/src/components/Term"; |
| 7 | +import OpenInCodeSandbox from "@site/src/components/OpenInCodeSandbox"; |
| 8 | + |
| 9 | +## 定数 |
| 10 | + |
| 11 | +これまで、変数の宣言には `let` キーワードを使用してきました。ところが、JavaScript の変数は、大抵初回代入以降は再代入が行われません。 |
| 12 | + |
| 13 | +再代入が行われない変数は `const` を用いて宣言することができます。このようにして宣言された変数を定数と呼び、定数への代入は宣言時にしか行えません。 |
| 14 | + |
| 15 | +```javascript |
| 16 | +// let で宣言した変数は再代入できる |
| 17 | +let variable = 1; |
| 18 | +variable = 2; |
| 19 | + |
| 20 | +const constant = 1; |
| 21 | +// const で宣言した変数に再代入しようとするとエラー |
| 22 | +// constant = 2; |
| 23 | +``` |
| 24 | + |
| 25 | +:::tip `let` と `const` |
| 26 | +ほとんどの場合、`const` が用いられたプログラムは `let` 書き換えても動作します。それでは、あえて `const` を用いる理由は何なのでしょうか。 |
| 27 | + |
| 28 | +JavaScript において、それはコードを読んだ際に読みやすいからです。`const` で定義されている変数なら、宣言文さえ見れば変数の中に入っている値を知ることができます。`const` が使用できる場所では、基本的に全て `const` を用いるようにしましょう。 |
| 29 | +::: |
| 30 | + |
| 31 | +:::info オブジェクトと `const` |
| 32 | +`const` による宣言で禁止されるのはその変数への代入だけであり、オブジェクトのプロパティへの代入はこれにあたりません。 |
| 33 | + |
| 34 | +```javascript |
| 35 | +const person = { name: "田中", age: 18 }; |
| 36 | +person.name = "佐藤"; // OK |
| 37 | +``` |
| 38 | + |
| 39 | +::: |
| 40 | + |
| 41 | +## 参照 |
| 42 | + |
| 43 | +[オブジェクト](../../1-trial-session/10-object/index.md)で扱ったように、JavaScript の値はオブジェクトとプリミティブに分けられます。前回は、プリミティブを「それ以上分解できない値」のように説明しました。もう少し詳しくみてみましょう。 |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | +次のコードを実行してみてください。 |
| 48 | + |
| 49 | +```javascript |
| 50 | +const object1 = { name: "田中", age: 18 }; |
| 51 | +const object2 = object1; |
| 52 | +object2.age = 19; |
| 53 | +document.write(object1.age); |
| 54 | +``` |
| 55 | + |
| 56 | +<OpenInCodeSandbox path="/docs/2-javascript-training/01-constant/samples/reference" /> |
| 57 | + |
| 58 | +このプログラムの実行結果は `19` になります。なぜでしょうか。 |
| 59 | + |
| 60 | +実は、オブジェクトを生成する式 `{ name: "田中", age: 18 }` は、オブジェクトを生成こそするものの、**式自体の評価結果は、オブジェクトそのものでなく、コンピューターのメモリ上のどこかに存在するオブジェクトの本体の場所を指し示す値になります**。 |
| 61 | + |
| 62 | +言い換えれば、JavaScript において、**オブジェクトそのものは値ではありません**。JavaScript の**値として有効なのは、オブジェクトへの参照**なのです。 |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +これを踏まえて先ほどのコードを見直してみましょう。JavaScript で値として扱えるのは参照のみなので、1 行目で `object1` に代入されるのは、その本体への参照です。 |
| 67 | + |
| 68 | +2 行目では、変数 `object1` に代入されている参照が `object2` にコピーされます。これにより、同じオブジェクトを参照する変数が 2 つできます。よって、`object1.age` と `object2.age` は同じものになるのです。 |
| 69 | + |
| 70 | +## ネストされたオブジェクト |
| 71 | + |
| 72 | +オブジェクトの中に別のオブジェクトが格納されている場合を考えてみましょう。 |
| 73 | + |
| 74 | +```javascript |
| 75 | +const person = { |
| 76 | + name: "田中", |
| 77 | + scores: { math: 80, science: 90 }, |
| 78 | +}; |
| 79 | +``` |
| 80 | + |
| 81 | +[以前](../../1-trial-session/10-object/index.md)にも記載した通り、オブジェクトのプロパティ名として使用可能なのは文字列のみですが、プロパティの値としては任意の JavaScript の値が使用できるのでした。 |
| 82 | + |
| 83 | +オブジェクトがネストされている場合、次のようにプロパティの値として別のオブジェクトへの参照が格納されていると考えることができます。 |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | +## 課題 |
| 88 | + |
| 89 | +参照の仕組みが特に問題になってくる場合として、オブジェクトの参照先が別の関数によって書き換えられる場合があります。次のコードを実行してみましょう。 |
| 90 | + |
| 91 | +```javascript |
| 92 | +function incrementAge(person) { |
| 93 | + person.age = person.age + 1; |
| 94 | +} |
| 95 | + |
| 96 | +const tanaka = { name: "田中", age: 18 }; |
| 97 | +const nextYearTanaka = incrementAge(tanaka); |
| 98 | +document.write(nextYearTanaka.age); |
| 99 | + |
| 100 | +// 19 と表示されてしまう |
| 101 | +document.write(tanaka.age); |
| 102 | +``` |
| 103 | + |
| 104 | +<OpenInCodeSandbox path="/docs/2-javascript-training/01-constant/samples/object-mutated-by-function" /> |
| 105 | + |
| 106 | +このコードは、[オブジェクト](../../1-trial-session/10-object/index.md)の項で扱った課題でした。実はこのコードには問題があり、`tanaka` に対して `incrementAge` を適用すると、関数が適用された `tanaka` にも影響が及んでしまいます。これは、関数に渡される値はオブジェクトへの参照で、このオブジェクトは呼び出し元の変数が参照するものと同一のものだからです。 |
| 107 | + |
| 108 | +`incrementAge` 関数の実装を変更し、関数に渡したオブジェクトが書き換えられないようにしてください。 |
| 109 | + |
| 110 | +:::tip |
| 111 | +オブジェクトが書き換えられないようにするためには、オブジェクトを新しく作り直す必要があります。 |
| 112 | +::: |
| 113 | + |
| 114 | +<details> |
| 115 | + <summary>解答</summary> |
| 116 | + <div> |
| 117 | + <p>オブジェクトを新しく生成して、生成したオブジェクトを返しましょう。</p> |
| 118 | + <CodeBlock language="javascript">{` |
| 119 | +function incrementAge(person) { |
| 120 | + return { name: person.name, age: person.age + 1 }; |
| 121 | +};\n |
| 122 | +const tanaka = { name: "田中", age: 18 }; |
| 123 | +const nextYearTanaka = incrementAge(tanaka); |
| 124 | +document.write(nextYearTanaka.age);\n |
| 125 | +// 18 |
| 126 | +document.write(tanaka.age); |
| 127 | + `.trim()}</CodeBlock> |
| 128 | + <OpenInCodeSandbox path="/docs/2-javascript-training/01-constant/samples/answer" /> |
| 129 | + </div> |
| 130 | +</details> |
0 commit comments