JavaScript Primer - 迷わないための入門書 (jsprimer)を読んで
はじめに
JavaScript Primer - 迷わないための入門書 (jsprimer)の感想を記載いたします。
良かったところ
- JS は、暗黙的な型変換についてかなりアバウトであることを学ぶことができました。
- 特に NaN は一度発生すると挙動的にもデバッグ的にもかなり厄介なため、NaN の防止がとりわけ重要であることを学べてよかったです。
難しかったこと
- this の扱いや、非同期処理、アロー関数の定義等一度読んだだけでは理解しきれない内容が多かったです。JS のコーディングをしていく中で辞書的に都度確認していこうと思います。
学んだこと
第一部:基本文法
- JavaScript は ECMAScript の使用によって動作が決められている。ECMAScript は毎年更新される。
- 変数宣言時に
var
を使用するのは現在非推奨。ECMAScript では機能を追加する際にも後方互換性を重視しているため、var
自体の挙動は変更されなかった。 - REPL(Read-Eval-Print-Loop)
データ型とリテラル
typeof null
が"object"
となるのは、歴史的経緯のある仕様のバグ。- テンプレートリテラル内で$
{変数名}
と書いた場合に、その変数の値を埋め込むことができる。
const str = "文字列"; console.log(`これは${str}です`); // => "これは文字列です"
undefined
はリテラルではない。undefined
はただのグローバル変数で、undefined
という値を持っているだけ。- JS において、ラッパーオブジェクトを明示的に作成する必要はない。ラッパーオブジェクトを使わずとも、ラッパーオブジェクトのプロパティを使用できる。常にリテラルでプリミティブ型のデータを表現すること推奨。
演算子
- NaN
- 数値ではないが Number 型。
- NaN はどの値とも(NaN 自身に対しても)一致しない特性がある。
Number.isNaN
メソッドを使うことで NaN の判定を行える。
// 自分自身とも一致しない console.log(NaN === NaN); // => false // Number型である console.log(typeof NaN); // => "number" // Number.isNaNでNaNかどうかを判定 console.log(Number.isNaN(NaN)); // => true
- 厳密等価演算子
const value = undefined; /* または null */ // === では2つの値と比較しないといけない if (value === null || value === undefined) { console.log("valueがnullまたはundefinedである場合の処理"); } // == では null と比較するだけでよい if (value == null) { console.log("valueがnullまたはundefinedである場合の処理"); }
- 分割代入
const obj = { "key": "value" }; // プロパティ名`key`の値を、変数`key`として定義する const { key } = obj; console.log(key); // => "value"
- falsy な値
- 以下は全て暗黙的な型変換で
false
に変換される。これらをfalsy
な値という。false
undefined
null
0
0n
NaN
""
(空文字列)
- 以下は全て暗黙的な型変換で
- Nullish coalescing 演算子(
??
)
const inputValue = 任意の値または未定義; // ||を使った場合 // `inputValue`がfalsyの場合は、`value`には`42`が入る // `inputValue`が`0`の場合は、`value`に`42`が入ってしまう const value = inputValue || 42; // ??を使った場合 // `inputValue`がnullishの場合は、`value`には42が入る // `inputValue`が`0`の場合は、`value`に`0`が入る const value = inputValue ?? 42; console.log(value);
暗黙的な型変換
- JS は、型エラーに対して暗黙的な型変換をしてしまうなど、驚くほど曖昧さを許容している。
- そのため、大きなアプリケーションを書く場合は、このような検出しにくいバグを見つけられるように書くことが重要となる。
- 真偽値の true が数値の 1 へと暗黙的に変換されてから加算処理
// 暗黙的な型変換が行われ、数値の加算として計算される 1 + true; // => 2
- JS では、エラーが発生するのではなく、暗黙的な型変換が行われてしまうケースが多くある。
- 暗黙的に変換が行われた場合、プログラムは例外を投げずに処理が進むため、バグの発見が難しくなる。
- 暗黙的な型変換はできる限り避けるべき挙動です。
Boolean への型変換
- 真偽値については、暗黙的な型変換のルールが少ないため、明示的に変換せずに扱われることも多い。
String への型変換
- 配列には join メソッド、オブジェクトには JSON.stringify メソッドなど、より適切な方法がある。
- そのため、String コンストラクタ関数での変換は、あくまでプリミティブ型に対してのみに留めるべき。
- シンボル →String への型変換
"文字列と" + Symbol("シンボルの説明"); // => TypeError: can't convert symbol to string
数値への型変換(主に NaN について)
- Number コンストラクタ関数、
Number.parseInt
、Number.parseFloat
は、 数字以外の文字列を渡すと NaN を返す。
// 数字ではないため、数値へは変換できない Number("文字列"); // => NaN // 未定義の値はNaNになる Number(undefined); // => NaN
- 任意の値から数値へ変換した場合には、NaN になってしまった場合の処理を書く必要がある。
- 変換した結果が NaN であるかは
Number.isNaN(x)
メソッドで判定可能。
const userInput = "任意の文字列"; const num = Number.parseInt(userInput, 10); if (Number.isNaN(num)) { console.log("パースした結果NaNになった", num); }
- NaN は何と演算しても結果は NaN になる特殊な値。次のように、計算の途中で値が NaN になると、最終的な結果も NaN となる。
const x = 10; const y = x + NaN; const z = y + 20; console.log(x); // => 10 console.log(y); // => NaN console.log(z); // => NaN
- NaN しか持っていない特殊な性質として、自分自身と一致しないというものがある。
- 実際に値が NaN かを判定する際には、
Number.isNaN(x)
メソッドを利用するとよい。 NaN は暗黙的な型変換の中でももっとも避けたい値となる。
- 理由として、先ほど紹介したように NaN は何と演算しても結果が NaN となってしまうため。
- これにより、計算していた値がどこで NaN となったのかがわかりにくく、デバッグが難しくなる。
意図しない NaN への変換を避ける方法として、大きく分けて2つの方法がある。
- 関数側(呼ばれる側)で、Number 型の値以外を受けつけなくする
- 関数を呼び出す側で、Number 型の値のみを渡すようにする
- つまり、呼び出す側または呼び出される側で対処するということ。どちらも行うことによってより安全なコードとなる。
- 上記の方法を行うためには、定義した関数が数値のみを受けつけるということを明示する必要がある。
- 明示する方法として以下の方法がある。
- 関数のドキュメント(コメント)として記述する。
- 引数に数値以外の値がある場合は例外を投げるという処理を追加する。
/** * 数値を合計した値を返します。 * 1つ以上の数値と共に呼び出す必要があります。 * @param {...number} values * @returns {number} **/ function sum(...values) { return values.reduce((total, value) => { // 値がNumber型ではない場合に、例外を投げる if (typeof value !== "number") { throw new Error(`${value}はNumber型ではありません`); } return total + Number(value); }, 0); }
明示的な変換でも解決しないこと
- あらゆるケースが明示的な変換で解決できるわけではない。
- Number 型と互換性がない値を数値にしても、
NaN
となってしまう。 - 一度、
NaN
になってしまうとNumber.isNaN(x)
で判定して処理を終えるしかない。
- Number 型と互換性がない値を数値にしても、
- JS の型変換は基本的に情報が減る方向へしか変換できない。
- そのため、明示的な変換をする前に、まず変換がそもそも必要なのかを考える必要がある。
空文字列判定について
- 以下のように Boolean で明示的な型変換をすると、falsy である
0
も空文字列となってしまい意図しない挙動になる。
// 空文字列かどうかを判定 function isEmptyString(str) { // `str`がfalsyな値なら、`isEmptyString`関数は`true`を返す return !Boolean(str); }
- 文字列とは「String 型で文字長が 0 の値」であると定義することで、空文字判定の関数をより正確に書くことができる。
- 次のように実装することで、値が空文字列であるかを正しく判定できるようになる。
// 空文字列かどうかを判定 function isEmptyString(str) { // String型でlengthが0の値の場合はtrueを返す return typeof str === "string" && str.length === 0; } console.log(isEmptyString("")); // => true // falsyな値でも正しく判定できる console.log(isEmptyString(0)); // => false console.log(isEmptyString()); // => false
関数と宣言
- 定義した関数の仮引数よりも呼び出し時の引数が少ない場合、余った仮引数には undefined という値が代入される。
- デフォルト引数を使わないで
||
を使って初期化するとバグにつながるので、デフォルト引数を使うべき。
文と式
- JS は、文(Statement)と式(Expression)から構成される。
オブジェクト
- プロパティの存在を確認するには in 演算子か Object.hasOwn 静的メソッドを使う
プロトタイプオブジェクト
- Object.hasOwn 静的メソッドは、指定したオブジェクト自体が指定したプロパティを持っているかを判定する。
- 一方、in 演算子はオブジェクト自身が持っていなければ、そのオブジェクトの継承元である prototype オブジェクトまで探索して持っているかを判定する。
関数と this
- this はオブジェクト指向プログラミングの文脈で JavaScript に導入された。
- メソッド以外においても this は評価できるが、実行コンテキストや strict mode などによって結果が異なり、混乱の元となる。
- そのため、メソッドではない通常の関数においては this を使うべきではない。
- コールバック関数における this は Arrow Function を使うことでわかりやすく解決できる。