【実務・中級編】 型判定ライブラリの比較(lodash.is等) – JavaScript実践ガイド

お疲れ。最近、コードレビューをしていて「また `typeof` の罠にハマってるな……」って思うことがやたら多いんだよね。

君も経験ないかい?
APIから飛んできたレスポンスを処理する時、あるいは汎用的なユーティリティ関数を書く時に、渡された変数が本当に「プレーンなオブジェクト」なのか、「配列」なのか、それとも「null」なのかを正確に判定したくて、気づけば `typeof` と `instanceof` の海を彷徨っている夜が。

今回は、JavaScriptにおける型判定の闇と、自前実装がなぜ地雷原になりやすいのか、そして `lodash.is` や `is.js` といった既存のプロたちが作ったライブラリをどう選び、どう使い倒すべきかについて、現場のリアルな視点から徹底的に解説していくよ。

コーヒーでも飲みながら、じっくり聞いていってくれ。

—

なぜJavaScriptの型判定はこれほどまでに泥臭いのか?

JavaScriptの型システムは、良くも悪くも「自由」だ。プロトタイプベースのオブジェクト指向であり、弱型言語(動的型付け)である以上、ランタイムまで変数の本当の姿は見えない。

そして、JavaScriptの歴史を語る上で避けて通れないのが、あの有名な言語仕様のバグだ。

console.log(typeof null); // “object” (伝説のバグ。未だに修正されていない)
console.log(typeof NaN); // “number” (数値ではないのに「数値」判定)

`typeof null` が `”object”` を返すのは、初期のJavaScriptにおける値の表現方法(タグ付きポインタ)の歴史的遺産だ。これを直すと世界中のWebサイトが壊れるから、永遠に修正されない。
つまり、僕たちはこの歪んだ土台の上で、堅牢なフロントエンドアプリケーションを作らなきゃいけないわけだ。

ここに、自前で型判定関数を書こうとするエンジニアが踏みがちなしこみがある。

—

自前実装の限界:なぜ「ちょっとした判定関数」がバグの温床になるのか?

「配列かどうかを判定するだけなら、`Array.isArray()` があるし、オブジェクトなら `typeof x === ‘object’` で十分でしょ?」
そう思った君、ちょっと待ってほしい。

実務で遭遇する「厄介なデータたち」を甘く見てはいけない。
例えば、以下のようなケースを考えてみよう。

1. iframeをまたいだデータ: 別ドメインのiframeから渡された配列は、親ウインドウの `Array.prototype` を共有していないため、`arr instanceof Array` は無情にも `false` を返す。
2. プリミティブのラッパーオブジェクト: `new String(‘hoge’)` なんて書く狂人は少ないが、サードパーティ製ライブラリが変なオブジェクトを返してくることは普通にある。
3. DOMノードやWindowオブジェクト: これらも `typeof` やプロパティの検証すり抜けの常習犯だ。

これらを自前ですべて安全にカバーしようとすると、コードはこうなる。

// 現場でよく見る「自前で頑張りすぎて逆にメンテ不能になった型判定関数」
function isReallyPlainObject(value) {
if (value === null || typeof value !== ‘object’) {
return false;
}

// プロトタイプのチェック
const proto = Object.getPrototypeOf(value);
if (proto === null) {
return true; // Object.create(null) のケース
}

const Ctor = Object.prototype.hasOwnProperty.call(proto, ‘constructor’) && proto.constructor;
return typeof Ctor === ‘function’ && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}

……どうだい? このコード、テストケースをいくつ書かなきゃいけない気がする?
これメンテするの、正直しんどいよね。車輪の再発明も大概にしておかないと、ビジネスロジックを書く前に頭がパンクしてしまう。

—

ライブラリの真価:`lodash.is` は裏側で何をやっているのか?

そこで登場するのが、`lodash` の型判定メソッド群(`_.isObject`, `_.isPlainObject`, `_.isArray` など)や、特化型ライブラリの `is.js` だ。

これらがなぜ信頼できるかと言うと、「JavaScriptの仕様の重箱の隅をつつくようなバグや、ブラウザ間の差異(クロスブラウザ問題)を、世界中の変態的なエンジニアたちが何年もかけて踏み潰してきた歴史の結晶」だからだ。

例えば、`lodash` の `_.isPlainObject` のソースコードを覗いてみると、オブジェクトのコンストラクターの判定、プロトタイプチェーンの走査、さらには古いIE(流石にもうサポート外だろうけど)やWebKitのバグに対するワークアラウンドがこれでもかと詰め込まれている。

パフォーマンスの懸念について

「ライブラリを入れると重くなるのでは?」という懸念を持つ中級者も多い。
確かに、全盛期の巨大会津的 `lodash` を丸ごとインポートして `_.isPlainObject` を呼ぶのは、バンドルサイズの観点からナンセンスだ。

しかし、現代のモジュールbundler(ViteやWebpack)と、lodashの個別パッケージ(例: `lodash.isplainobject`)や、ESMに対応したモダンなユーティリティを使えば、オーバーヘッドはほぼ無視できるレベルに抑えられる。
さらに言えば、自前のバグだらけの判定関数で無限ループや予期せぬレンダリングバグを踏むコストに比べたら、ライブラリのサイズなど安い投資だ。

—

実践:現場ですぐに使える型判定ベストプラクティス

じゃあ、実務ではどうコードを書くべきか?
「ネイティブで安全に書けるものはネイティブを使い、エッジケースが多い複雑な判定は信頼できるライブラリに逃げる」というハイブリッド戦略が、最もプロフェッショナルで現実的だ。

以下のコードを見てほしい。そのままコピーして、プロジェクトの `utils/typeCheck.ts` あたりに貼ってもらっても構わない。

/

  • 実務で即戦力となる型判定・ユーティリティの実装例
  • ※必要に応じて lodash 等の信頼できるライブラリの関数をラップ・採用する前提

/

/

  • 1. プリミティブおよび基本的な判定はネイティブの機能で十分高速かつ安全に処理できる

/
export const isString = (val: unknown): val is string => typeof val === ‘string’;
export const isNumber = (val: unknown): val is number => typeof val === ‘number’ && !Number.isNaN(val);
export const isBoolean = (val: unknown): val is boolean => typeof val === ‘boolean’;
export const isFunction = (val: unknown): val is Function => typeof val === ‘function’;

/

  • 2. 配列は Array.isArray が ECMAScript 5.5以降の標準であり、iframeの壁も超えられるためこれ一択

/
export const isArray = Array.isArray;

/

  • 3. 「値が存在するか(nullでもundefinedでもない)」の判定
  • TypeScriptの Type Guard (is) を活用して後続の処理で型を絞り込む

/
export const isDefined = (val: T | null | undefined): val is T => {
return val !== null && val !== undefined;
};

/

  • 4. 複雑な「プレーンオブジェクト({} で作られたただのハッシュマップ)」の判定
  • ここを自前で実装するのはフラグなので、信頼の置けるライブラリ(例: lodash.isplainobject)のロジックを採用する

/
import _isPlainObject from ‘lodash.isPlainObject’;

export const isPlainObject = (val: unknown): val is Record => {
return _isPlainObject(val);
};

// — 実際の使用例 —
function processApiResponse(response: unknown) {
// 存在チェック
if (!isDefined(response)) {
console.error(‘レスポンスが空です’);
return;
}

// プレーンオブジェクトかどうかの厳密なチェック
if (!isPlainObject(response)) {
console.warn(‘想定外のデータ型が渡されました’, typeof response);
return;
}

// ここから先は安全にプロパティにアクセスできる
console.log(‘処理成功:’, response.status);
}

—

シニアからのアドバイス:型判定で迷ったら

最後に、チームの後輩である君に一つアドバイスを送ろう。

コードを書いている時、「この変数が想定通りの型か不安だな」と感じたら、それは大抵の場合、設計のどこかに不整合があるサインだ。
TypeScriptを導入しているプロジェクトであれば、型定義(Type Definition)と実際のランタイムの値が乖離している箇所(APIレスポンスのパース部分や、外部ライブラリの境界線)でのみ、厳密な型ガードを行えばいい。

すべての関数内部で `typeof` や `instanceof` をゴリゴリ書く必要はない。
「境界線(Boundary)では徹底的に疑い、内部のロジックではTypeScriptの型システムを信じる」。これがモダンなフロントエンド開発の鉄則だ。

そして、その「境界線」での厳密なチェックにおいて、自前の怪しいコードで消耗するのはもう終わりだ。
信頼できるライブラリの知恵を借りるか、今回紹介したような枯れたベストプラクティスをチームで共通化し、もっと本質的なUIやビジネスロジックの構築にリソースを割いていこう。

それじゃ、次のコードレビューを楽しみにしているよ!

コメント

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