Programming Field

typeof 演算子 - TypeScriptキーワード一覧

スポンサーリンク

typeof キーワードのうち、「式の値(評価された値)が属する型の種類を文字列で返す演算子」としての typeof について説明しています。

概要

「typeof」キーワードは、単一の式を受け付ける演算子(単項演算子)として扱われます。

typeof expression

expression に式を指定すると、この演算子を含む全体の結果は expression の値に基づいた型の名前(文字列)となります。この型の名前は 'undefined' や 'object' など仕様で定められており、これを利用してオブジェクトの内容を細かく判定することは出来ませんが、宣言されていない変数名を指定することができるため、「undefined かどうか」などの処理の場合分けにおいては重要な役割を持ちます。

typeof 演算子の仕様

構文

(参照: ES2015 - 12.5.6 The typeof Operator)

UnaryExpression: typeof UnaryExpression
UnaryExpression: typeof UnaryExpression
キーワード「typeof」に続けて単一の式を指定します。変数、定数、オブジェクトのメンバー、関数の戻り値などが考えられます。
式には原則として括弧「( )」は不要ですが、式の中に「+」などの別の演算子を含める場合は「( )」で括る必要があります。
なお、「typeof ...」の式全体はそれ自身が単一の式として扱われます。(理論上「typeof typeof ...」などとすることもできますが、ほぼ意味を成しません。)

意味

この演算子の評価結果は、「typeof」に続く UnaryExpression の評価結果に基づいて以下の文字列になります。

評価結果文字列TSでの型のnarrow(制約)先
(存在しない変数などの解決できない参照)'undefined'undefined
「undefined」値'undefined'undefined
「null」値'object'(状況依存)
「Boolean」値'boolean'boolean
「Number」値'number'number
「Symbol」値 (ES2015以前には存在しません)'symbol'symbol
「String」値'string'string
呼び出し可能なデータ(「[[Call]]」内部フィールドを実装)'function'Function
外部データ(スクリプトエンジンが提供するもの)(実装依存)(なし)
その他'object'(状況依存、上記に挙げた型を除き、nullを含む)

※ 「null 値」は 'null' にはなりません。「null 値」かどうかの判定方法は後述の詳細およびnull キーワードもご覧ください。

なお、TypeScriptでは上述の仕様に基づき、評価結果の型が「'string' | 'number' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function'」になります(TypeScript PR #13791)。これに加え、「型ガード」を行う演算子の1つとして以下のように利用されます(参照: TypeScript/spec.md 「4.24 Type Guards」)。

  • typeof x === s のガード(条件式)において s が 'string', 'number', 'boolean' (仕様には明記は無いが 'symbol', 'undefined', 'object', 'function' も利用可能) のいずれかとき:
    • ガードが true のとき、x は「x の型のsubtype(部分型)」という条件のもと(s に)対応するプリミティブ型(および Function 型)として扱われます。
      • 「『x の型のsubtype』という条件で」という制約があるため、「x の型のsubtype」にプリミティブ型が入らない場合は「never 型」となります。
    • ガードが false のとき、x の型から対応するプリミティブ型が取り除かれます。
  • typeof x === s のガード(条件式)において s が上記の場合を除く文字列のとき:
    • このケースの場合、s が「typeof」の結果の型に当てはまらないため、「常に結果が false となる式」と扱われエラーになります。
  • typeof x !== s のガード(条件式)において s が文字列のとき:
    • ガードが true のとき、x は「typeof x === s」の false の場合に基づいた型の制約(narrow)が行われます。
    • ガードが false のとき、x は「typeof x === s」の true の場合に基づいた型の制約が行われます。

キーワード情報

詳細

通常の式における「typeof」演算子は、後続する式の結果がどんな型になるかを大雑把に文字列で返す演算子です。素のJavaScript(ECMAScript)においても型をチェックするために用いることができる演算子ですが、TypeScriptではこれに加えて「型ガード」の仕様が含まれるため、以下のように「typeof」演算子を使うことで「複数の型の可能性がある変数」から特定の型に対する処理のみを行えるような条件分岐を書くことができっます。

let myForm: HTMLFormElement;

// 第1引数が文字列または数値を受け付ける関数
function getMyFormElement(nameOrIndex: string | number) {
    if (typeof nameOrIndex === 'string') {
        // ここでは nameOrIndex は「string」型として扱える
        // (「namedItem」は「string」型を受け付けるので型ガードが無いと指定できない)
        return myForm.elements.namedItem(nameOrIndex);
    } else {
        // ここでは nameOrIndex は「number」型として扱える
        // (「item」は「number」型を受け付けるので型ガードが無いと指定できない)
        return myForm.elements.item(nameOrIndex);
    }
}

※ JSでも上記のような「number」や「string」などの判定を行うことは可能であり、特に上記のように呼び出すメソッドが変わる場合などはこのような分岐処理を書くことができます。なお、JSでは誤って条件分岐をせずに実装した場合実際に実行するまで気付かなくなる可能性があります。(TypeScriptは実装の誤りを静的に判定・検出できるというメリットがあります。)

注意点としては、「typeof」では「undefined」の判定は可能ですが、「null」の判定はできないため、

// 第1引数が省略可能でnullable、かつ文字列または数値を受け付ける関数
function performSomething(data?: string | number | null) {
    if (typeof data !== 'undefined') {
        // ここでは data はまだ null である可能性がある
        pushText(data.toString()); // ERROR: TS2531: null の可能性がある
    }
}

というコードではエラーになります。そのため、別途nullかどうかの判定を行う必要があります。

// 第1引数が省略可能でnullable、かつ文字列または数値を受け付ける関数
function performSomething(data?: string | number | null) {
    if (typeof data !== 'undefined' && data !== null) {
        // ここでは data は「string」または「number」のどちらかに絞られる
        pushText(data.toString()); // 「string」も「number」を引数なしの「toString」が使えるので常に文字列を渡すことができる
    }
}

※ 上記における「typeof data !== 'undefined'」という記述は「data !== void 0」または(よほど特殊な環境でない限り)「data !== undefined」とも記述することができます。ただしtypeof data !== 'undefined' && data !== null」という判定式を単純に「data」としてしまうと、「undefined」や「null」だけでなく、文字列の「''」や数値の「0」までも除外してしまうため注意が必要です。(「data != null」でも可能ですが、「==」や「!=」はあまり推奨されない傾向にあります。)

特に、利用したい変数が「unknown 型」である場合、そのままでは利用できず型ガードがほぼ必須になるため、「typeof」を使う必要が生じる可能性があります。

let myObj: any;

// 第1引数が「string 型」か「number 型」か「symbol 型」のいずれかであれば特定の値を、
// それ以外の場合は undefined を返す関数
function getByData(data: unknown) {
    // 「unknown 型」はそのままでは利用できないので型ガードを行う
    if (typeof data === 'string' || typeof data === 'number' || typeof data === 'symbol') {
        // 「string」「number」「symbol」であればプロパティーキー(インデックスシグネチャ)として利用できる
        return myObj[data];
    } else {
        // それ以外の場合は undefined とする
        return void 0;
    }
}

なお、「typeof」は「Functionかどうか」、すなわち「呼び出し可能(callable)かどうか」の判定にも利用できます。(多くの場合、「呼び出し可能(callable)かどうか」の判定に限り、「instanceof」を利用した判定(「x instanceof Function」)も可能です。)

// 何かが設定されている変数
let p: unknown;
  .
  .
  .
// callableかどうか判定(「p instanceof Function」でも基本的には可)
if (typeof p === 'function') {
    // 「p」がcallableなので呼び出し構文が利用可能
    p();
}

※ ES2015の仕様上、「typeof x === 'function'」と「x instanceof Function」は直接的にはイコールではありませんが、少なくともES2015の仕様上すべてのcallableなデータは「Function」オブジェクトのprototypeまたはその派生を持つことになっているため、実質的にはほぼ等価に利用できます。ただし、関数オブジェクトのprototypeが「Object.setPrototypeOf」で書き換えられている場合はこの限りではありません。