JavaScript 型別檢查 (typeof / instanceof)

JavaScript 是動態型別語言,變數的型別可以在執行時改變。本篇介紹如何檢查值的型別。

typeof 運算子

typeof 運算子回傳一個字串,表示運算元的型別:

typeof 123;           // 'number'
typeof 'hello';       // 'string'
typeof true;          // 'boolean'
typeof undefined;     // 'undefined'
typeof Symbol();      // 'symbol'
typeof 123n;          // 'bigint'
typeof function() {}; // 'function'
typeof {};            // 'object'
typeof [];            // 'object'
typeof null;          // 'object' (歷史遺留的 bug)

typeof 的回傳值

型別typeof 結果
Number'number'
String'string'
Boolean'boolean'
Undefined'undefined'
Symbol'symbol'
BigInt'bigint'
Function'function'
其他物件(含 Array、null)'object'

typeof 的用途

檢查變數是否已定義

// 安全地檢查變數是否存在
if (typeof myVar !== 'undefined') {
    // myVar 已定義
}

// 直接使用可能會報錯
if (myVar !== undefined) {  // ReferenceError(如果 myVar 未宣告)
}

檢查基本型別

function processValue(value) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    } else if (typeof value === 'number') {
        return value * 2;
    } else if (typeof value === 'boolean') {
        return !value;
    }
}

typeof 的陷阱

null 的問題

typeof null;  // 'object'(這是 JavaScript 的歷史 bug)

// 正確檢查 null
var value = null;
if (value === null) {
    console.log('是 null');
}

無法區分物件類型

typeof {};        // 'object'
typeof [];        // 'object'
typeof new Date(); // 'object'
typeof /regex/;   // 'object'

// typeof 無法區分這些不同的物件類型

instanceof 運算子

instanceof 用來檢查物件是否是某個建構函式的實例(檢查原型鏈):

var arr = [1, 2, 3];
var obj = { a: 1 };
var date = new Date();

arr instanceof Array;    // true
arr instanceof Object;   // true(Array 也繼承自 Object)
obj instanceof Object;   // true
date instanceof Date;    // true
date instanceof Object;  // true

自訂建構函式

function Person(name) {
    this.name = name;
}

var mike = new Person('Mike');

mike instanceof Person;  // true
mike instanceof Object;  // true

instanceof 的限制

跨 frame/window 失效

在瀏覽器中,不同 frame 或 window 的 Array 是不同的建構函式:

// 從 iframe 取得的陣列
var iframeArray = iframe.contentWindow.Array;
var arr = new iframeArray(1, 2, 3);

arr instanceof Array;  // false(因為是不同的 Array)

對基本型別無效

'hello' instanceof String;  // false
123 instanceof Number;      // false

// 只有包裝物件才會是 true
new String('hello') instanceof String;  // true
new Number(123) instanceof Number;      // true

Array.isArray()

最可靠的陣列檢查方法:

Array.isArray([1, 2, 3]);  // true
Array.isArray('hello');    // false
Array.isArray({ length: 3 }); // false

// 即使跨 frame 也能正確判斷
Array.isArray(iframeArray);  // true

Object.prototype.toString.call()

最通用的型別檢查方法,可以準確區分各種內建物件:

function getType(value) {
    return Object.prototype.toString.call(value);
}

getType(123);          // '[object Number]'
getType('hello');      // '[object String]'
getType(true);         // '[object Boolean]'
getType(undefined);    // '[object Undefined]'
getType(null);         // '[object Null]'
getType([]);           // '[object Array]'
getType({});           // '[object Object]'
getType(function(){}); // '[object Function]'
getType(new Date());   // '[object Date]'
getType(/regex/);      // '[object RegExp]'
getType(new Map());    // '[object Map]'
getType(new Set());    // '[object Set]'

可以包裝成更實用的函式:

function typeOf(value) {
    return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

typeOf(123);      // 'number'
typeOf([]);       // 'array'
typeOf(null);     // 'null'
typeOf(new Date()); // 'date'

constructor 屬性

每個物件都有 constructor 屬性,指向建立它的建構函式:

var arr = [];
var obj = {};
var str = 'hello';

arr.constructor === Array;    // true
obj.constructor === Object;   // true
str.constructor === String;   // true
constructor 屬性可以被修改,所以不是最可靠的檢查方式。

實用的型別檢查函式

var is = {
    // 基本型別
    string: function(value) {
        return typeof value === 'string';
    },
    
    number: function(value) {
        return typeof value === 'number' && !isNaN(value);
    },
    
    boolean: function(value) {
        return typeof value === 'boolean';
    },
    
    undefined: function(value) {
        return typeof value === 'undefined';
    },
    
    null: function(value) {
        return value === null;
    },
    
    // 物件型別
    array: function(value) {
        return Array.isArray(value);
    },
    
    object: function(value) {
        return value !== null && typeof value === 'object' && !Array.isArray(value);
    },
    
    function: function(value) {
        return typeof value === 'function';
    },
    
    date: function(value) {
        return value instanceof Date && !isNaN(value);
    },
    
    regexp: function(value) {
        return value instanceof RegExp;
    },
    
    // 特殊檢查
    empty: function(value) {
        if (value === null || value === undefined) return true;
        if (Array.isArray(value) || typeof value === 'string') return value.length === 0;
        if (typeof value === 'object') return Object.keys(value).length === 0;
        return false;
    },
    
    numeric: function(value) {
        return !isNaN(parseFloat(value)) && isFinite(value);
    }
};

// 使用範例
is.string('hello');     // true
is.array([1, 2, 3]);    // true
is.empty({});           // true
is.numeric('123');      // true

比較總結

方法優點缺點
typeof簡單、快速無法區分物件類型,null 回傳 'object'
instanceof可檢查原型鏈跨 frame 失效,基本型別無效
Array.isArray()準確檢查陣列只能檢查陣列
Object.prototype.toString.call()最準確、通用語法較冗長
constructor可以檢查自訂類型可被修改,不可靠

選擇建議

  • 檢查基本型別(string、number、boolean):使用 typeof
  • 檢查陣列:使用 Array.isArray()
  • 檢查自訂類別的實例:使用 instanceof
  • 需要精確區分內建物件類型:使用 Object.prototype.toString.call()