typeof vs instanceof

177 阅读4分钟

JavaScript 是一门弱类型的语言,在定义变量的时候,不能事先声明变量的类型。变量的类型只有真正运行代码的时候才知道。所以如果不做类型判断的话,就可能出现类似下面的错误:

function sum(a, b) {
    return a + b;
}
sum(1, 1)   // 2
sum('1', 1) // '11'
sum()       // NaN

虽然在运行 sum('1',1)sum() 的时候,函数并没有报错(这是因为 JavaScript 的自动类型转换机制),但是传给函数的参数显然不符合我们的预期。我们期望 ab 是数字,而不是其他数据类型。

为了解决这个问题,我们需要对变量的类型进行判断。JavaScript 提供了两个表达式 typeofinstanceof 来帮助我们判断数据类型。在具体了解这两个表达式之前,我们需要先了解下 JavaScript 有哪些数据类型。

JavaScript 的数据类型

JavaScript 的数据类型分为基本数据类型和引用数据类型。

基本数据类型

基本数据类型一共有 7 种,分别是:

  • String
  • Number
  • Boolean
  • undefined
  • null
  • Symbol
  • BigInt

基本数据类型的特点是值不可修改,例如:

// s 属于 String 类型, 它的值是不可修改的
let s = 'hello,world'
s[0] = 'x'
console.log(s) // hello,world
引用数据类型

引用数据类型只有 Object,当然 JavaScript 还提供了很多继承自 Object 的内置数据类型,比如

  • Function
  • Array
  • Date
  • RegExp
  • ...

typeof

typeof 是一个表达式,用法是:typeof x ,返回值表示 x 的数据类型。

let a = 1;
if (typeof a === 'number') {
  // do something...
}

typeof 1           // 'number'
typeof 1n          // 'bigint'
typeof true        // 'boolean'
typeof 'hello'     // 'string'
typeof null        // 'object'
typeof undefined   // 'undefined'
typeof Symbol()    // 'symbol'
typeof {}          // 'object'
typeof []          // 'object'
typeof new Date()  // 'object'
typeof (() => {})  // 'function'

对于基本数据类型, typeof 能够进行很好的区分。唯一的例外是 null,返回的是 'object'

对于引用数据类型, typoef 只能区分出 Function

所以 typeof 能够判断数据类型是否属于 Number, String, Boolean, undefined, Symbol 等基本数据类型或者 Function。对于其他的数据类型, typeof 无法做出区分。

现在,我们可以对 sum 函数进行改进,在传入非数字参数的时候,抛出错误:

/* JavaScript */
function sum(a, b) {
    if (typeof a !== 'number') {
        throw new Error('a 不是数字!')
    }
    if (typeof b !== 'number') {
        throw new Error('b 不是数字!')
    }
    return a + b;
}
sum(1, 1)   // 2
sum('1', 1) // error
sum().      // error

现在,当传入函数的参数不是数字时, sum 函数便会报错。

但是,考虑下面这种情况:

function sum(a, b) {
    if (typeof a !== 'number') {
        throw new Error('a 不是数字!')
    }
    if (typeof b !== 'number') {
        throw new Error('b 不是数字!')
    }
    return a + b;
}
let a = new Number(1);
let b = 1;
sum(a, b) // error

a 被包裹在 Number 对象里, typeof 判断失效了。

instanceof

instanceof 用来判断一个对象是否属于某个类的实例,用法: a instanceof b,且 b 必须是一个构造函数,如果 a 属于 b 的实例,返回 true,否则返回 false

[] instanceof Array;            // true
({}) instanceof Object;         // true
new Date() instanceof Date;     // true
[] instanceof Date;             // false
new Number() instanceof Number; // true
[] instanceof Number;           // false

使用 instanceof ,我们就可以区分出 Date , Array 等引用类型了。

sum 函数也可以进一步完善:

function sum(a, b) {
    if (typeof a !== 'number' && !(a instanceof Number)) {
        throw new Error('a 不是数字!')
    }
    if (typeof b !== 'number' && !(b instanceof Number)) {
        throw new Error('b 不是数字!')
    }
    return a + b;
}
let a = new Number(1);
let b = 1;
sum(a, b) // 2

BigInt

BigInt 是 ES2020 引入的基本数据类型,用来支持任意精度的数字运算,使用的时候,在常规的数字后面加上字母 n 或者使用 BigInt() 函数来生成。

function sum(a, b) {
    if (typeof a !== 'number' && !(a instanceof Number)) {
        throw new Error('a 不是数字!')
    }
    if (typeof b !== 'number' && !(b instanceof Number)) {
        throw new Error('b 不是数字!')
    }
    return a + b;
}
let a = 1n;
let b = 1n;
sum(a, b) // error

在上面的例子中, sum 报错了,是因为 typeof 1n 返回的是 bigint, 并且, BigInt 属于基本数据类型, 不属于 Number 对象的实例。要兼容 BigInt 的话,我们还需要做额外的判断:

function sum(a, b) {
    if (typeof a !== 'bigint' && typeof a !== 'number' && !(a instanceof Number)) {
        throw new Error('a 不是数字!')
    }
    if (typeof b !== 'bigint' && typeof b !== 'number' && !(b instanceof Number)) {
        throw new Error('b 不是数字!')
    }
    return a + b;
}
let a = 1n;
let b = 1n;
sum(a, b) // 2n