JavaScript 中的类型判断:`typeof`, `instanceof` 与 `Object.prototype.toString`

好的,各位听众老爷们,晚上好!欢迎来到“JavaScript类型判断漫谈”现场,我是今晚的主讲人,人送外号“Bug终结者”。今天咱们不搞那些枯燥乏味的理论,咱们要用段子和案例,把typeofinstanceofObject.prototype.toString 这三个JavaScript界的“老炮儿”给扒个底朝天,看看它们各自的能耐和局限,以及如何在实战中巧妙地运用它们。

首先,咱们先来热热场,讲个笑话:

程序员A:我最近写了个判断类型的函数,贼牛!
程序员B:哦?怎么个牛法?
程序员A:不管什么类型,都能准确判断!
程序员B:那你判断下自己是什么类型的?
程序员A:……(陷入沉思)

这个笑话告诉我们,类型判断这玩意儿,看似简单,实则水很深。搞不好,就把自己给绕进去了。

第一章:typeof:江湖人称“类型速递员”,但经常送错件儿

typeof,顾名思义,就是“类型是啥”的意思。它是一个一元运算符,就像一个快递员,你把一个变量扔给它,它会告诉你这个变量是什么类型的。但是,这个快递员经常送错件儿,尤其是遇到一些特殊情况。

咱们先来看看typeof的正常表现:

typeof 123;       // "number"
typeof "hello";   // "string"
typeof true;      // "boolean"
typeof undefined;  // "undefined"
typeof Symbol();   // "symbol" (ES6新增)
typeof 123n;      // "bigint" (ES2020新增)

这些都是基础类型,typeof 可以准确无误地识别出来,就像老司机认路一样,稳得很。

但是,当遇到引用类型时,typeof就开始犯迷糊了:

typeof {};       // "object"
typeof [];       // "object"
typeof null;     // "object"  😱  (这是一个著名的bug!)
typeof function(){}; // "function"

看到没?除了函数(function)能被准确识别之外,其他的引用类型,比如对象(object)、数组(array)和 null,都被无情地打上了“object”的标签。这就像快递员把所有的包裹都贴上“杂物”的标签,简直是丧心病狂!

为什么 typeof null 会返回 "object" 呢?

这是一个历史遗留问题,也是JavaScript设计上的一个缺陷。在JavaScript的早期版本中,所有值都是以32位存储的,其中前几位表示类型标签。null 的类型标签是全零,而对象的类型标签也是以零开头。为了性能考虑,typeof 直接检查了 null 的类型标签,结果就误判为 "object" 了。

这个bug一直保留至今,主要是为了兼容旧代码。所以,以后遇到 null,千万不要相信 typeof,它会忽悠你的!

总结一下 typeof 的优缺点:

特性 优点 缺点
返回值 字符串,表示变量的类型 对于引用类型,除了函数外,都返回 "object";typeof null 返回 "object",这是一个bug
适用场景 判断基本类型(number、string、boolean、undefined、symbol、bigint) 无法准确判断引用类型的具体类型
使用方式 typeof variabletypeof(variable)

第二章:instanceof:身世调查员,追根溯源,但认亲戚有局限

instanceof 是一个运算符,用来判断一个对象是否是某个构造函数的实例。它就像一个身世调查员,根据对象的原型链,追溯其祖宗八代,看看它是不是某个家族的成员。

举个例子:

let arr = [];
arr instanceof Array;        // true
arr instanceof Object;       // true  (因为Array的原型链上也有Object)

let obj = {};
obj instanceof Object;       // true

function Person(name) {
  this.name = name;
}
let person = new Person("张三");
person instanceof Person;    // true
person instanceof Object;    // true  (因为Person的原型链上也有Object)

可以看到,instanceof 不仅会判断对象是否是直接的构造函数的实例,还会沿着原型链向上查找,如果原型链上有该构造函数,也会返回 true

instanceof 的工作原理:

instanceof 运算符的左边是一个对象,右边是一个构造函数。它会检查左边对象的原型链上是否存在右边构造函数的 prototype 属性。如果存在,则返回 true,否则返回 false

但是,instanceof 也有它的局限性:

  1. 无法判断基本类型: instanceof 只能用于判断对象,不能用于判断基本类型。

    123 instanceof Number;   // false  (虽然123是数字,但它不是Number对象的实例)
  2. 原型链被修改的影响: 如果修改了对象的原型链,instanceof 的结果可能会不准确。

    function Animal() {}
    function Dog() {}
    Dog.prototype = new Animal();  // Dog继承自Animal
    let dog = new Dog();
    
    dog instanceof Animal;   // true
    dog instanceof Dog;      // true
    
    // 修改原型链
    Dog.prototype = {};
    dog instanceof Animal;   // false  (原型链被修改,Animal不在dog的原型链上了)
  3. 跨iframe的问题: 在多个iframe之间,由于每个iframe都有自己的全局环境,构造函数也不一样,instanceof 可能会失效。

    <!-- iframe1.html -->
    <script>
        let arr = [];
    </script>
    
    <!-- iframe2.html -->
    <script>
        let iframe1 = document.getElementById('iframe1');
        let arr = iframe1.contentWindow.arr; // 获取iframe1中的数组
    
        arr instanceof Array;  // false  (因为iframe2中的Array构造函数和iframe1中的Array构造函数不一样)
    </script>

总结一下 instanceof 的优缺点:

特性 优点 缺点
返回值 布尔值,表示对象是否是某个构造函数的实例 无法判断基本类型;原型链被修改会影响结果;跨iframe可能失效
适用场景 判断对象是否是某个构造函数的实例,尤其是在继承关系中 尽量避免在原型链被修改或者跨iframe的情况下使用
使用方式 object instanceof Constructor

第三章:Object.prototype.toString:类型识别的“照妖镜”,终极解决方案

Object.prototype.toString 是一个终极武器,它可以准确地识别任何类型的值,包括基本类型和引用类型。它就像一面“照妖镜”,可以把各种妖魔鬼怪都照出原形。

为什么 Object.prototype.toString 这么厉害?

因为 Object.prototype.toString 方法会返回一个表示该对象的字符串,格式为 "[object Type]",其中 Type 是对象的类型。这个类型是由JavaScript引擎内部决定的,不会受到原型链的影响,也不会受到跨iframe的影响。

如何使用 Object.prototype.toString

我们需要使用 callapply 方法来改变 this 的指向,让 Object.prototype.toString 方法作用于我们要判断类型的值。

Object.prototype.toString.call(123);       // "[object Number]"
Object.prototype.toString.call("hello");   // "[object String]"
Object.prototype.toString.call(true);      // "[object Boolean]"
Object.prototype.toString.call(undefined);  // "[object Undefined]"
Object.prototype.toString.call(null);     // "[object Null]"
Object.prototype.toString.call({});        // "[object Object]"
Object.prototype.toString.call([]);        // "[object Array]"
Object.prototype.toString.call(function(){}); // "[object Function]"
Object.prototype.toString.call(new Date());   // "[object Date]"
Object.prototype.toString.call(new RegExp()); // "[object RegExp]"
Object.prototype.toString.call(Symbol());   // "[object Symbol]"
Object.prototype.toString.call(123n);      // "[object BigInt]"

可以看到,Object.prototype.toString 可以准确地识别各种类型的值,包括 nullundefined,简直是类型判断界的“良心”。

封装一个通用的类型判断函数:

为了方便使用,我们可以把 Object.prototype.toString 封装成一个通用的类型判断函数:

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

getType(123);       // "Number"
getType("hello");   // "String"
getType(true);      // "Boolean"
getType(undefined);  // "Undefined"
getType(null);     // "Null"
getType({});        // "Object"
getType([]);        // "Array"
getType(function(){}); // "Function"
getType(new Date());   // "Date"
getType(new RegExp()); // "RegExp"
getType(Symbol());   // "Symbol"
getType(123n);      // "BigInt"

这个 getType 函数可以准确地判断任何类型的值,而且不会受到原型链和跨iframe的影响,简直是类型判断界的“瑞士军刀”。

总结一下 Object.prototype.toString 的优缺点:

特性 优点 缺点
返回值 字符串,表示对象的类型,格式为 "[object Type]" 需要使用 callapply 方法来改变 this 的指向,稍微繁琐
适用场景 准确判断任何类型的值,包括基本类型和引用类型
使用方式 Object.prototype.toString.call(value)

第四章:实战演练:类型判断的最佳实践

现在,我们已经了解了 typeofinstanceofObject.prototype.toString 这三个类型判断工具的特性,接下来,我们来看看如何在实战中巧妙地运用它们。

1. 判断基本类型:

对于基本类型,我们可以使用 typeof,简单快捷。

function isString(value) {
  return typeof value === "string";
}

isString("hello");   // true
isString(123);       // false

2. 判断引用类型:

对于引用类型,尤其是需要区分具体类型的时候,我们应该使用 Object.prototype.toString

function isArray(value) {
  return Object.prototype.toString.call(value) === "[object Array]";
}

isArray([]);        // true
isArray({});        // false

3. 判断自定义类型:

对于自定义类型,我们可以使用 instanceof,方便快捷地判断对象是否是某个构造函数的实例。

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

function isPerson(value) {
  return value instanceof Person;
}

let person = new Person("张三");
isPerson(person);   // true
isPerson({});        // false

4. 综合运用:

在实际项目中,我们可能需要综合运用这三个工具,根据不同的场景选择最合适的方案。

function getType(value) {
  if (typeof value === "number") {
    return "Number";
  } else if (typeof value === "string") {
    return "String";
  } else if (typeof value === "boolean") {
    return "Boolean";
  } else if (typeof value === "undefined") {
    return "Undefined";
  } else if (typeof value === "symbol") {
    return "Symbol";
  } else if (typeof value === "bigint") {
    return "BigInt";
  } else if (value === null) {
    return "Null";  // 特别注意:typeof null === "object",所以要单独判断
  } else if (value instanceof Array) {
    return "Array";
  } else if (value instanceof Date) {
    return "Date";
  } else if (value instanceof RegExp) {
    return "RegExp";
  } else if (typeof value === "function") {
    return "Function";
  } else {
    return "Object";
  }
}

getType(123);       // "Number"
getType("hello");   // "String"
getType([]);        // "Array"
getType(new Date());   // "Date"
getType(null);     // "Null"
getType({});        // "Object"

第五章:总结:类型判断的武林秘籍

经过今天的漫谈,相信各位听众老爷们对JavaScript中的类型判断已经有了更深入的了解。下面,我给大家总结一份类型判断的“武林秘籍”,希望对大家有所帮助:

  • typeof 擅长判断基本类型,但对引用类型束手无策,typeof null 是个坑。
  • instanceof 擅长判断对象是否是某个构造函数的实例,但在原型链被修改或跨iframe的情况下会失效。
  • Object.prototype.toString 类型判断的终极武器,可以准确识别任何类型的值,但使用起来稍微繁琐。

记住:

  • 没有万能的工具,只有最合适的工具。
  • 根据不同的场景选择最合适的类型判断方案。
  • 多实践,多思考,才能真正掌握类型判断的精髓。

好了,今天的“JavaScript类型判断漫谈”就到这里,感谢各位听众老爷们的捧场!下次再见!😊

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注