JavaScriptガイド
言語内部、ランタイム、フレームワーク — コードレベルで動作原理整理
言語内部
イベントループ — setTimeout(fn, 0)が即座に実行されない理由
Call Stack、Task Queue、Microtask Queueの実行順序
JavaScriptはシングルスレッドなのに非同期が可能な理由:イベントループがCall Stackが空の時にTask Queueからコールバックを取り出して実行するから。Promiseのthenは Microtask Queueに入り通常Taskより先に実行される。
クロージャとスコープチェーン — 関数が外部変数を記憶する原理
Lexical Environmentオブジェクトが作るスコープチェーンの内部構造
クロージャは関数が生成された時点のLexical Environmentをキャプチャして保存するもの。関数が外部スコープの変数を参照すると、そのLexical Environmentオブジェクトがガベージコレクションされず生き残る。
プロトタイプチェーン — classがなかった時代の継承
__proto__、prototype、Object.create()の関係
JavaScriptのclassはシンタクティックシュガー。内部的にプロトタイプチェーンで動作する。オブジェクトの__proto__が親オブジェクトを指し、プロパティが見つからなければチェーンを辿り上がる。
Promise内部 — then()がMicrotask Queueに入る過程
Promiseの3つの状態とthenableチェーンの実行メカニズム
Promiseはpending/fulfilled/rejectedの3状態を持つ状態マシン。resolve()呼び出し時にthenに登録されたコールバックがMicrotask Queueに入る。async/awaitはPromiseのシンタクティックシュガー。
TypeScript
TypeScript型システム — コンパイル後全て消える
構造的型付け(Structural Typing)とは何か、Javaの名目的型付けとなぜ違うか
TypeScriptの型はコンパイル時にのみ存在し、ランタイムJavaScriptには痕跡も残らない。Javaと違い「名前」ではなく「構造」で型互換性を判断する(Structural Typing)。
TypeScriptジェネリクスパターン — Tが何か一発で理解する
関数/クラス/インターフェースで「後で決まる型」を使う方法
Tは「まだ分からない型」のプレースホルダー。関数呼び出し時点でTが具体型に決まる。ジェネリクスなしではanyを使うことになり、anyを使うとTypeScriptの意味がなくなる。
判別ユニオン — TypeScriptでenumの代わりに使うパターン
typeフィールドで分岐するとTypeScriptが各分岐の型を自動的に絞る
共通フィールド(discriminant)でユニオン型を区別するパターン。switch/ifで分岐するとTypeScriptが該当分岐で型を自動的に絞る。Rustのenum + matchに近い効果。
Node.js
Node.jsイベントループ — ブラウザと異なる6段階フェーズ
timers → pending → idle → poll → check → close、そしてprocess.nextTickの位置
Node.jsのイベントループ(libuv)はブラウザと違い6つのフェーズを巡回する。setTimeoutはtimersフェーズ、setImmediateはcheckフェーズ、process.nextTickは全フェーズ間で実行。
Node.js Streams — 大容量データをメモリ爆発なしで処理する方法
Readable、Writable、Transform + backpressureの動作原理
10GBファイルをfs.readFile()で読むとメモリが爆発する。Streamはデータをchunk単位で流してメモリを一定に維持。Backpressureは消費者が遅い時に生産者を止めるメカニズム。
ESM vs CommonJS — requireとimportが違う理由
同期ロード vs 非同期ロード、そして"type": "module"の意味
CommonJS(require)は同期ロード、ESM(import)は非同期ロード。Node.jsで両システムが共存することで生じる互換性問題と解決策。
ブラウザAPI
Web Workers — JavaScriptでマルチスレッドを使う方法
postMessageの構造化複製(Structured Clone)とSharedArrayBuffer
Web Workerはメインスレッドと別のスレッドでJavaScriptを実行する。DOMアクセス不可、postMessageで通信。重い計算をWorkerに回せばUIが止まらない。
Service Workerライフサイクル — install、activate、fetchの順序
PWAの核心、オフラインキャッシュが動作するメカニズム
Service Workerはブラウザとネットワークの間にプロキシとして動作する。install→activate→fetchの順でライフサイクルが進行し、Cache APIでリソースを保存してオフラインでもアプリが動作するようにする。
Intersection Observer — スクロールイベントなしで要素の可視性を検出
lazy loading、無限スクロール、アニメーショントリガーの高性能実装方法
Intersection Observerは要素がビューポートに入る/出る時にコールバックを実行する。scrollイベント+getBoundingClientRect()より性能が良く、ブラウザが最適化されたタイミングで実行。
Popover APIとは?JSなしでツールチップを開閉する方法
popover属性1つで開閉・Escキー・アクセシビリティが自動動作するブラウザネイティブUI
従来のツールチップにはイベントリスナー、状態管理、ARIA属性の手動同期が全て必要だった。Popover APIはこれをブラウザネイティブで置き換える。popover属性とpopovertargetだけで開閉・Escキー・キーボードナビゲーションが自動動作。
Cookie・localStorage・sessionStorage — 三つの質問で決まる
同じ画面でフォルダツリーはCookie、スクロール位置はsessionStorageに分かれた理由
サーバーが読むか、最初のHTMLに反映が必要か、タブを閉じても残すか。この順に聞けばほぼ決まる。トークンのような秘密値は質問せずHttpOnly Cookieへ。
event.target vs currentTarget — アイコンの線の上を押した時だけ起きるバグ
data-* を読むならcurrentTarget。SVGは塗られた線だけがクリックを受ける
リスナーを付けた要素のdata-*を読むならevent.currentTargetを使う。event.targetは実際に押された一番内側の要素なので、ボタン内のSVGアイコンの線を押すと<path>が取れて値がundefinedになる。
Web Audio
setIntervalでドラムを鳴らすとリズムが揺れる — 「今鳴らせ」ではなく「何秒に鳴らせ」
JSタイマーは起こすだけ、音はオーディオの時計で予約するlookaheadスケジューラ
ブラウザでリズムを合わせるなら、タイマーのコールバック内ですぐ音を出さず、25msごとに起きて0.1秒先までの音をAudioContextの時刻で予約する。同じ妨害条件でsetIntervalは114〜136msで揺れ、予約方式は0.5秒間隔のままだった。
イヤホンの「左だけ」は二種類 — StereoPanner(寄せる)とChannelSplitter(選ぶ)
pan: -1は右の音を捨てずに左へ合わせる。実測で見た違い
イヤホンは受け取ったL/Rを鳴らすだけ。Webで「左だけ」を作るとき、StereoPannerNodeのpan: -1は右の音を左へ寄せ、ChannelSplitterは左チャンネルだけを選んで右を捨てる。どちらも右は無音だが、左で何が聞こえるかが違う。
オーディオのテストは画面ではなくAnalyserNodeで — バックグラウンドタブで17個すべて「無音」と出た理由
絵は止まっても音は鳴る。ピーク値を測ればクリッピングまで見つかる
ブラウザ自動化でオーディオデモを検査するときにキャンバスの波形を見ると、バックグラウンドタブではrequestAnimationFrameが止まり、全部無音と誤判定する。AnalyserNodeの時間領域データからピークを測れば、音の有無とクリッピング(1超え)の両方がわかる。実際にクリッピングを3件見つけた。
Strudelはビートのスケッチには最強、曲の完成には弱い — そしてlazy importするとエフェクトが静かに消える
アプリに組み込んで使った感想と、「次のmousedown」を待つオーディオ初期化の罠
Strudelはループをその場でいじるのは強いが、曲を最後まで作ったりサービスに組み込んだりするのは弱い。アプリに入れるときバンドル(約659KB)が大きいのでクリック時にdynamic importしたら、オーディオ初期化がすでに過ぎたmousedownを待ち続け、shapeのようなAudioWorkletエフェクトがエラー表示なしに抜けた。mousedownを自分で一度発火させれば直る。
WASM・3D
「100%ローカル」はメディアファイルの話 — WhisperとffmpegをWASMに押し込んだ代償
ブラウザ完結の動画エディタrescriptを実際に動かして確かめたこと
「100%ローカル」のエディタは、動画・音声ファイルをサーバーに送らないという意味で、何も出ていかないという意味ではない。rescriptは利用統計とクラッシュレポートが既定でオンだった。ローカルにした代償は、初回のモデルダウンロードと、サーバーがCOOP/COEPヘッダーを送る必要があるSharedArrayBuffer要件として返ってくる。
.blendはWebで開けない — Blender→Webは変換ではなく「GPUが食べられるものだけ残す翻訳」
3Dゲームを作ったのにモデルファイルが0個の理由まで
.blendはBlenderのメモリをほぼそのままダンプしたファイルなので、Webでは開けない。glTFにexportするとモディファイア・ジオメトリノード・Cyclesノード・物理は焼き込まれるか消え、Webは三角形・バッファ・PBRの値だけを受け取って絵を一から描き直す。うちの3Dかくれんぼゲームは歩きを距離基準で縛る必要があったので、GLBではなくコードで箱を組み立てた。
オープンソース分析
background-removal-jsとは?ブラウザだけで画像の背景を削除して透明PNGにする仕組み
IS-Net ONNXモデル + WASM推論 + Canvas API、サーバーなしでクライアント完結するパイプライン
background-removal-js(imgly)は、画像の背景を削除して透明PNGで返すJavaScriptライブラリ。IS-NetモデルをONNX Runtime WASMでブラウザ内実行するので、画像をサーバーに送らない。デコードからPNG出力までの5段階をコードで追う。
OpenGenerativeUI — ClaudeのVisual Artifactsをオープンソースで再現した構造
CopilotKit Deep Agents + LangGraph + サンドボックスiframe、AIがチャート・3D・ダイアグラムをリアルタイム生成するパイプライン
CopilotKitがClaudeのインタラクティブVisual Artifacts生成をオープンソースで実装した。Next.js 16フロントエンド + Python LangGraphエージェントがHTML/SVG/Chart.js/Three.jsコードを生成、サンドボックスiframeでレンダリング。MITライセンス。Star 1.1K。
Pretext — DOMなしでテキスト高さを測定する純粋JSレイアウトライブラリ
Canvas measureText()で文字幅を取得し、行計算は純粋な算術演算のみ — レイアウトリフロー0
ブラウザでテキストが何行になるか調べるには通常getBoundingClientRectやoffsetHeightを使うが、これはレイアウトリフローを強制する。PretextはCanvas measureText()でフォントエンジンから直接文字幅を取得し、行計算はキャッシュされた幅値での純粋な算術演算のみ。DOM一切アクセスなし。500テキストバッチでlayout()が0.09ms。React・Relay創始者chenglou氏のプロジェクト。⭐ 7.1K。