js中如何判断数据类型

发布时间 2023-04-11 11:24:44作者: Programing_Monkey

1. typeof

可以使用 typeof 运算符来判断一个值的类型。typeof 运算符返回一个表示值类型的字符串,可能的值包括:

  • "undefined":表示未定义的值;
  • "boolean":表示布尔值;
  • "number":表示数值;
  • "string":表示字符串;
  • "symbol":表示 ES6 引入的 Symbol 类型;
  • "bigint":表示 ES10 引入的 BigInt 类型;
  • "object":表示对象(包括数组、函数和 null);
  • "function":表示函数。
typeof undefined;  // "undefined"
typeof true;       // "boolean"
typeof 123;        // "number"
typeof "hello";    // "string"
typeof Symbol();   // "symbol"
typeof 123n;       // "bigint"
typeof null;       // "object"
typeof {};         // "object"
typeof [];         // "object"
typeof function() { /* ... */ };  // "function"

缺点:

  • typeof null 返回的是 "object"

    • 在 JavaScript 的早期版本中,它使用 32 位的内存来表示一个值,其中低 3 位用于标识类型信息。这 3 位的值分别为:

      • 000:表示对象类型(包括函数)
      • 010:表示浮点数类型;
      • 100:表示字符串类型;
      • 110:表示布尔类型;
      • 1:表示整数类型,即除了上面 4 种类型之外的其他类型。

      注意,这个类型系统是非常简单的,只有 5 种类型,而且都是由低 3 位标识的。

      在这个类型系统中,对象类型的标识是 000,但是由于 null 被当作一个空对象指针,它的二进制表示全是 0,因此它被错误地识别为对象类型,而非整数类型。

      后来随着 JavaScript 的发展,内存模型变得更加复杂,同时也引入了更多的类型,但是 typeof null 返回 "object" 这个错误的结果已经成为了 JavaScript 的一种行为,为了保持向后兼容性,这个行为就一直被保留了下来。因此,虽然 null 不是对象,但是 typeof null 仍然返回 "object"

  • 不能区分不同对象类型之间的差异。比如无法区分数组和普通对象,因为它们的 typeof 都是 "object"

2. instanceof

  • 使用

用于判断对象是否为某个类的实例

其语法如下:

object instanceof constructor

其中 object 是要检测的对象,constructor 是构造函数或类。如果 objectconstructor 的实例或者是 constructor 的子类的实例,返回 true,否则返回 false

console.log(true instanceof Boolean)  // false
console.log(123 instanceof Number)  // false
console.log("hello" instanceof String)  // false
console.log(Symbol() instanceof Symbol) // false
console.log(123n instanceof BigInt) // false
console.log(([]) instanceof Array);  //true
console.log(({}) instanceof Object); //true
console.log((function() { /* ... */ }) instanceof Function); //true

可以看到,基本数据类型使用instanceof判断会出错。因此,不能使用instanceof判断基本数据类型。

  • 原理

instanceof 的原理是通过判断 object 的原型链中是否存在 constructor.prototype。如果存在,则返回 true,否则返回 false

在js中,使用instanceof会调用原型上的内置函数Symbol.hasInstance,举例如下

class Bar {}
class Baz extends Bar {
    static [Symbol.hasInstance]() {
        return false
    }
}

let b = new Baz()
console.log(Bar[Symbol.hasInstance](b)) // true
console.log(b instanceof Bar)   // true
console.log(Baz[Symbol.hasInstance](b)) // false
console.log(b instanceof Baz)   // false
  • instanceof 代码实现
function myInstanceOf(obj, constructor) {
  let prototype = Object.getPrototypeOf(obj);
  while (prototype) {
    if (prototype === constructor.prototype) {
      return true;
    }
    prototype = Object.getPrototypeOf(prototype);
  }
  return false;
}
  • 缺点

    • 无法正确判断基本数据类型。

    • 只能判断对象是否为某个类的实例,不能判断对象的具体数据类型,比如一个数组是无法使用 instanceof 判断的。

    • 如果对象的原型链太长,instanceof 的效率可能会比较低。

3. constructor

constructor是对象对应原型上的属性,指向构造函数。

  • 使用
console.log(new Number(123).constructor)
//ƒ Number() { [native code] }

可以看到它指向了Number的构造函数,因此,可以使用num.constructor==Number来判断一个变量是不是Number类型

true.constructor === Boolean;  // true
(123).constructor === Number;  // true
('hello').constructor === String;  // true
Symbol().constructor === Symbol; // true
(123n).constructor === BigInt; // true
({}).constructor === Object; // true
([]).constructor === Array;  // true
(function() { /* ... */ }).constructor === Function; // true
undefined.constructor === undefined; // TypeError: Cannot read property 'constructor' of undefined
null.constructor === null; // TypeError: Cannot read property 'constructor' of null

可以看到,除了undefined和null报错之外,其他类型都可以通过constructor属性来判断类型。

  • 缺点
    • 无法判断undefined和null
    • 不稳定,主要体现在自定义对象上,当开发者重写prototype后,原有的constructor会丢失,constructor会默认为Object。

4. Object.prototype.toString

​ toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型。

​ 那为什么要用Object原型上的toString方法呢?大部分的对象都实现了自身的toString方法,这样就可能会导致Object的toString被终止查找,因此可以call来强制执行Object的toString方法。

  • 使用
Object.prototype.toString.call(undefined)  // [object Undefined]
Object.prototype.toString.call(true)       // [object Boolean]
Object.prototype.toString.call(123)        // [object Number]
Object.prototype.toString.call("hello")    // [object String]
Object.prototype.toString.call(Symbol())   // [object Symbol]
Object.prototype.toString.call(123n)       // [object BigInt]
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]

可封装成函数

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

getType(true) // boolean
  • 原理

使用 Symbol.toStringTag 拼接,举例如下。

class Bar {}
let bar = new Bar()

// 修改前
console.log(bar.toString()) // [object Object]
bar[Symbol.toStringTag] = 'Bar'
// 修改后
console.log(bar.toString()) // [object Bar]