【実務・中級編】 typeof nullがobjectである歴史的背景 – JavaScript実践ガイド

お疲れ。ちょっとコーヒーでも飲みながら聞いてくれ。

フロントエンドの現場でバリバリコードを書いている君なら、一度はハマったことがあるはずだ。「あれ、なんで `null` の型チェックをしたのに `object` になるんだ?」ってね。

`.ts` が当たり前になった現代でも、ランタイムの足元をすくうのはいつだって素の JavaScript のこうした「歴史的遺物」だ。今回は、JavaScript 界最大のバグであり、今なお生き続ける歴史的ミステリー `typeof null === ‘object’` の正体と、現場で二度とバグを生み出さないための実践的な防御策について、シニアの視点から徹底的に解説しよう。

—

なぜ `typeof null` は `object` なのか?(歴史的背景)

犯人は、1995年にたった10日間で JavaScript(当時は LiveScript)を書き上げた伝説の男、Brendan Eich(ブレンダン・アイク)氏だ。当時、彼が何を考えていたのか、少し裏側の話をしよう。

1つの変数がメモリ上でどう表現されていたか

C言語ベースで実装された初期の JavaScript エンジンにおいて、すべての値は「タグ付きポインタ(Tagged Pointer)」という仕組みでメモリ上に表現されていた。これは、「値の先頭の数ビットを、それが何のデータ型であるかを示すタグ(目印)として使おうぜ」という、当時のC言語らしい超アグレッシブな最適化手法だ。

当時の型タグの割り振りは、おおむねこんな感じになっていた。

  • `000`: オブジェクト(Object)
  • `1`: 整数(Int)
  • `010`: 浮動小数点数(Double)
  • `100`: 文字列(String)
  • `110`: ブール値(Boolean)

お気づきだろうか?
JavaScript における `null` は、C言語におけるヌルポインタ(実質的なメモリ上のアドレス `0x00`、つまり全ビットが `0`)として表現されていた。

これを当時の `typeof` の実装(C言語の関数)に放り込んだとき、エンジンはこう判断した。

「おっ、この値の先頭のビット列は `000` だな? よってこいつはオブジェクト(Object)だな!」

これが、`typeof null` が `object` を返すようになったメカニズムの全貌だ。

なぜ今でも修正されないのか?

「いやいや、当時バグだったなら、今すぐ直せよ!」って思うよね。私も最初はそう思った。

しかし、もし現在の仕様(ECMAScript)でこれを『正しく `null` を返す』ように修正したらどうなるか。世の中の何百万、何千万という既存のウェブサイトやライブラリが一斉にクラッシュする。
「`typeof` の結果が変わっただけで動かなくなったレガシーアプリ」の山が築かれ、Webの基本理念である「後方互換性(Backward Compatibility)」が盛大に破壊されることになる。

TC39(JavaScriptの仕様策定委員会)も過去にこれを修正しようと提案した(Harmonyの初期段階)が、結局「既存のコードが壊れるリスクが高すぎる」という理由でボツになった。つまり、このバグは「永遠の仕様」として、私たちプログラマーが背負い続けなければならない業というわけだ。

—

現場で起きる「うっかりバグ」

この仕様をナメていると、実務で痛い目を見る。例えば、APIから取得したレスポンスのバリデーションや、オプション引数の型チェックを書くときだ。

// 現場でよく見る「危なっかしい」コード
function processUserData(data) {
// data がオブジェクトであることを確認したい
if (typeof data === ‘object’) {
// data が null の場合、ここで意図せず通ってしまう!
console.log(data.name);
}
}

processUserData(null); // TypeError: Cannot read properties of null (reading ‘name’)

`typeof null` が `’object’` を返すせいで、`null` チェックをすり抜けてオブジェクト用の処理に突入し、ランタイムエラー(Cannot read properties of null)を爆誕させる。TypeScript を使っていても、`any` や `unknown` が混ざるレガシーなコードベースや、外部APIの型ガード(Type Guard)の書き方次第では、この罠に普通に引っかかる。

—

実務ですぐに使えるベストプラクティス

じゃあ、我々フロントエンドエンジニアはどうやって身を守ればいいのか?
明日からそのままチームのコードベースに組み込める、堅牢な判定パターンを授けよう。

1. `null` と `object` を厳密に分離するユーティリティ

実務では、単に「オブジェクトかどうか」を判定したい場面が非常に多い。その場合は、`null` を明外する条件をセットにするのが鉄則だ。

/

  • 値が純粋なオブジェクト(配列や null を除く)であるかを判定する
  • @param {unknown} value
  • @returns {boolean}

/
export function isPlainObject(value) {
// typeof で object かつ、null でなく、配列でもないことを確認する
return value !== null && typeof value === ‘object’ && !Array.isArray(value);
}

// — 使用例 —
console.log(isPlainObject({ a: 1 })); // true
console.log(isPlainObject(null)); // false (ここで弾かれる!)
console.log(isPlainObject([1, 2, 3])); // false (配列も除外)
console.log(isPlainObject(‘string’)); // false

2. TypeScript における型ガード(Type Guard)での活用

もしプロジェクトで TypeScript を使っているなら、次のような型述語(Type Predicate)を用意しておくと、コンパイラも納得してくれるし、ランタイムも安全になる。

/

  • TypeScript用の厳密なオブジェクト型ガード

/
export function isValidObject(val: unknown): val is Record {
return val !== null && typeof val === ‘object’ && !Array.isArray(val);
}

// 実際のAPIハンドリングでの例
function handleApiResponse(response: unknown) {
if (isValidObject(response)) {
// このブロック内では response は Record として安全に扱える
console.log(response.status);
} else {
console.warn(‘不正なレスポンス形式です:’, response);
}
}

—

シニアからのメッセージ

言語の歴史的背景を知ることは、単なる「豆知識」や「飲み会のネタ」ではない。
「なぜこの言語はこういう挙動をするのか」という裏側のメカニズムを理解しているエンジニアは、バグを踏んだときに迷わないし、コードのレビューで危険な匂いを嗅ぎ取ることができる。

JavaScript は自由度が高く、歴史のツケを払わされている部分も多い言語だ。だからこそ、シニアとして一歩踏み込んだ知識を持ち、チーム全体を安全に導いていってほしい。

さて、コーヒーを飲み干したら、さっそくプロジェクトの `typeof` の使い方を見直してみようか。頼んだぞ!

コメント

タイトルとURLをコピーしました