JavaScript Primer - 迷わないための入門書 (jsprimer)を読んで

はじめに

JavaScript Primer - 迷わないための入門書 (jsprimer)の感想を記載いたします。

良かったところ

  • JS は、暗黙的な型変換についてかなりアバウトであることを学ぶことができました。
  • 特に NaN は一度発生すると挙動的にもデバッグ的にもかなり厄介なため、NaN の防止がとりわけ重要であることを学べてよかったです。

難しかったこと

  • this の扱いや、非同期処理、アロー関数の定義等一度読んだだけでは理解しきれない内容が多かったです。JS のコーディングをしていく中で辞書的に都度確認していこうと思います。

学んだこと

第一部:基本文法

  • JavaScriptECMAScript の使用によって動作が決められている。ECMAScript は毎年更新される。
  • 変数宣言時にvarを使用するのは現在非推奨。ECMAScript では機能を追加する際にも後方互換性を重視しているため、var自体の挙動は変更されなかった。
  • REPL(Read-Eval-Print-Loop)
    • コードを評価しその結果を表示する機能のこと。Chromeデベロッパーツールで叩けるコンソールもその一つ

データ型とリテラル

  • 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
  • 厳密等価演算子
    • 意図しない挙動となることがあるため、暗黙的な型変換が行われる等価演算子(==)を使うべきではない。
    • 代わりに、厳密等価演算子(===)を使い、異なる型を比較したい場合は明示的に型を合わせるべき。
    • 例外的に、等価演算子(==)が使われるケースとして、null と undefined の比較がある。
 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 演算子(??)
    • Nullish coalescing 演算子(??)は、左辺の値が nullish であるならば、右辺の評価結果を返す。
    • nullish とは、評価結果が null または undefined となる値のこと。
    • 0が falsy のため||では扱えない。この問題を解決するため??演算子が導入された。
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 への型変換
    • ES2015 で追加されたプリミティブ型であるシンボルは暗黙的に型変換できない。
    • 文字列結合演算子をシンボルに対して利用すると例外 TypeError を投げるようになっている。
    • そのため、片方が文字列であるからといってプラス演算子の結果は必ず文字列になるとは限らない。
"文字列と" + Symbol("シンボルの説明"); // => TypeError: can't convert symbol to string
数値への型変換(主に NaN について)
  • Number コンストラクタ関数、Number.parseIntNumber.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)で判定して処理を終えるしかない。
  • 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 を使うことでわかりやすく解決できる。