JavaScriptの高度な概念と実践的なテクニック

ブラウザの動作原理とV8エンジン

ブラウザカーネルとJSエンジンの関係

  • WebCore: HTMLの解析、レイアウト、レンダリングなどの関連作業を担当
  • JavaScriptCore: JavaScriptコードの解析と実行を担当

V8エンジンの原理

  • JavaScriptソースコード
    • 字句解析: ソースコード内の各単語のタイプ、値などの情報を解析
    • 構文解析: 単語のタイプ情報に基づいて構文解析を行い、抽象構文木を生成
  • AST抽象構文木(形式が固定されたツリー構造のオブジェクト)
  • MachineCode最適化されたマシンコード(複数回実行されるバイトコード関数はホット関数としてマークされ、固定のマシン命令セットに最適化されます)
  • バイトコード

JavaScriptの実行プロセス

グローバルコードの実行とスコープの昇格

  • 実行コンテキストスタック(ECStack)
    • グローバル実行コンテキスト(GEC)
      • 変数オブジェクト(VO) == グローバルオブジェクト(GO)
    • 関数実行コンテキスト(FEC)
      • 変数オブジェクト(VO) == 活性オブジェクト(AO)
      • スコープチェーン == AO + 親スコープ + GO
  • 宣言の昇格: 変数の代入や関数の実行が行われる前のプロセス
  • 遅延解析: グローバルスコープで実行される関数を事前に解析し、ネストされた関数は呼び出されたときに完全に解析される

JavaScriptのメモリ管理とクロージャ

一般的なGCアルゴリズム

  • 参照カウント
  • マーク・スイープ

クロージャ

  • 高階関数: 関数のパラメータまたは戻り値が関数である場合、その関数は高階関数と呼ばれます
  • クロージャ: 関数 + アクセス可能な自由変数

JavaScriptにおけるthisについて

グローバルスコープにおけるthis

  • ブラウザ: thisはwindowを指す
  • Node環境: thisは空のオブジェクト({})を指す

thisの4つのバインディングルール

  • デフォルトバインディング(fun()): 独立した関数呼び出しではthisはwindowを指す
  • 暗黙的バインディング(object.fun()): objectオブジェクトがjsエンジンによってfun関数内のthisにバインドされる
  • 明示的バインディング:
    • apply: fun.apply(object,[itemX])
    • call: fun.call(object,itemX)
    • bind: var funBind = fun.bind(object); funBind()
  • newバインディング: function Person(name) { this.name = name; } thisはコンストラクタnewによって作成されたオブジェクトを指す
  • 優先順位: newバインディング > 明示的バインディング(bind > call/apply) > 暗黙的バインディング > デフォルトバインディング

関数型プログラミング

純粋関数

  • 同じ入力は必ず同じ出力を生成する
  • 実行過程で副作用を一切生じない

カリー化

  • カリー化の利点: 上位層の関数ロジックの再利用
  • 簡略化した記述: var fun = x => y => z => x + y + z

汎用カリー化関数の実装

function autoCurrying(fn) {
    return function curried(...args1) {
        if (args1.length >= fn.length) {
            return fn.apply(this, args1);
        } else {
            return function (...args2) {
                return curried.apply(this, args1.concat(args2));
            };
        }
    };
}

function logMessage(time, level, message) {
    console.log(`[${time.getHours()}:${time.getMinutes()}][${level}][${message}]`);
}

const curriedLogger = autoCurrying(logMessage);
curriedLogger(new Date())("DEBUG")("これは日本語のメッセージです");
curriedLogger(new Date(), "DEBUG")("これは日本語のメッセージです");
curriedLogger(new Date(), "DEBUG", "これは日本語のメッセージです");

合成関数

  • 合成関数: 順次実行される複数の関数を一つにまとめる

汎用合成関数の実装

function compose(...fns) {
    const length = fns.length;
    for (let i = 0; i < length; i++) {
        if (typeof fns[i] !== "function") {
            throw new TypeError("引数は関数である必要があります");
        }
    }
    
    return function (...args) {
        let result = length ? fns[0].apply(this, args) : args;
        for (let i = 1; i < length; i++) {
            result = fns[i].call(this, result);
        }
        return result;
    };
}

function add(a, b) {
    return a + b;
}

function multiplyByTwo(c) {
    return c * 2;
}

const composedFn = compose(add, multiplyByTwo);
console.log(composedFn(1, 2)); // 6 (1+2=3, 3*2=6)

ES6クラス(class)

クラスとコンストラクタ関数

  • クラスはコンストラクタ関数のシンタックスシュガーであり、本質的な違いはない

クラスのコンストラクタ

  • クラスにはコンストラクタが一つしか存在できない
  • newキーワードでクラスを呼び出すと、新しいオブジェクトが作成され、thisがそのオブジェクトを指す

クラス内のメソッド

  • インスタンスメソッド: クラスのプロトタイプに定義される
  • アクセッサーメソッド: get/setキーワードを使用してプロパティのアクセスと設定を制御
  • 静的メソッド: staticキーワードを使用してクラス自体に関連付けられる

クラスの継承

  • extendsキーワードを使用して親クラスを継承
  • super()を使用して親クラスのコンストラクタを呼び出す
  • メソッドのオーバーライド: 子クラスで親クラスのメソッドを再定義

ES6からES5への変換

  • Babel: ES6+のコードをES5に変換するツール
  • #__PURE__#: 純粋関数をマークする。webpackのtree-shakingで未使用のコードを削除するために使用される

ES6の新機能

リテラルの強化された記述法

  • プロパティの省略記法: {name, age}
  • メソッドの省略記法: { method() {} }
  • 計算されたプロパティ名: [dynamicKey]

分割代入(Destructuring)

  • 配列の分割代入: const [a, b] = [1, 2]
  • オブジェクトの分割代入: const {name, age} = person
  • デフォルト値: const {name = "default"} = obj

テンプレート文字列

    li>複数行文字列のサポート
  • 式の埋め込み: `${expression}`
  • タ付きテンプレート: 関数をテンプレートの前に配置

デフォルト引数値

    li>関数の引数にデフォルト値を設定: function fn(param = "default")
  • デフォルト値を持つ引数は関数のlengthプロパティに含まれない

残余引数(Rest Parameters)

  • 不定数の引数を配列として受け取る: function fn(...args)
  • argumentsオブジェクトとの違い: 残余引数は実際の配列であり、アロー関数でも使用可能

アロー関数

  • 簡潔な関数構文: () => {}
  • 独自のthisを持たず、レキシカルスコープからthisを継承
  • コンストラクタとして使用できない

展開構文(Spread Syntax)

  • 関数呼び出し時: fn(...array)
  • 配列リテラル: [...array1, ...array2]
  • オブジェクトリテラル: {...obj1, ...obj2}

ES6の新しいデータ型とデータ構造

Symbol

  • 一意な値を表す新しい原始データ型
  • オブジェクトのプロパティキーとして使用可能
  • Symbol.for()でグローバルシンボルを登録・取得

Set

  • 重複を許さない値のコレクション
  • add(), delete(), has(), clear()などのメソッド
  • forEach()やfor...ofで反復可能

WeakSet

  • オブジェクトのみを格納できるSetの弱い参照版
  • ガベージコレクションによるオブジェクトの自動解放
  • 反復やサイズの取得は不可

Map

  • キーと値のペアのコレクション
  • 任意のデータ型をキーとして使用可能
  • Setと同様のメソッドをサポート

WeakMap

  • オブジェクトのみをキーとして使用できるMapの弱い参照版
  • ガベージコレクションによるキーオブジェクトの自動解放
  • 反復やサイズの取得は不可

ES7~ES12の新機能

  • ES7: Array.includes(), 指数演算子(**)
  • ES8: Object.values(), Object.entries(), String.padStart(), String.padEnd()
  • ES9: Promise.finally(), Object.fromEntries()
  • ES10: Array.flat(), Array.flatMap(), trimStart(), trimEnd()
  • ES11: BigInt, Nullish Coalescing(??), Optional Chaining(?.), Global This
  • ES12: FinalizationRegistry, WeakRef, 論理代入演算子(||=, &&=, ??=)

タグ: javascript V8エンジン 関数型プログラミング ES6 クラス

5月16日 20:50 投稿