你真的掌握 JavaScript 中的数据类型检测了吗?

573 阅读8分钟

这是我参与8月更文挑战的第6天,活动详情查看8月更文挑战

前言

JavaScript 中的数据类型,是学习 JavaScript 的基础,前面几篇都是围绕着数据类型,进行了解和学习,今天我们以掌握 JavaScript 中的数据类型检测为目标,开启我们的探索之旅。

JavaScript 检测数据类型的方法有哪些?

数据类型检测有如下方法:

  • tyepof [value] :检测数据类型的运算符
  • [example] instanceof [class] : 检测某一个实例是否属于这个类
  • [example].constructor===[class] :检测实例和类关系的,从而检测数据类型
  • Object.prototype.toString.call([value]):使用 toString 方法,返回字符串,比如 "[object Object]"

其实上面有些方法,是被临时拉壮丁,实际上并不是专门用于检测数据类型的。接下来,我们一一介绍每种方式,不管你是熟悉还是不熟悉,继续往下看,肯定有不一样的收获。

typeof

1、定义:用来检测数据类型的运算符,本质就是直接在计算机底层基于数据类型的值(二进制)进行检测

2、语法:tyepof [value]

3、返回值:

  • typeof 操作符返回一个字符串,所以检测的结果是一个字符串
  • 字符串表示 value 对应的数据类型,比如:"number"、"string"、"boolean"、"undefined"、"object"、"function"、"symbol"、"bigint"

注:

  • NaN / Infinity 都是数字类型,所以检测结果都是 "number"。
  • typeof 返回的结果永远是一个字符串(字符串中包含了对应的类型),所以连续出现两个及两个以上 typeof 检测的时候,最终结果都是 "string"。比如,typeof typeof [] 的结果,是 "string"
  • typeof function fn() {},返回的结果是 "function" mdn 中关于 typeof 返回值的说明:

image.png 4、优点:使用起来简单,可以有效检测出基本数据类型值和引用数据类型值

5、局限性「缺点」

  • typeof null 的结果是 "object"。原因:这是一个悠久的 bug,在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,对象存储在计算机中,都是以 000 开始的二进制存储,然而 null 表示为全零,所以将它错误的判断为 object。
  • 不能细分对象类型,typeof 普通对象/数组对象/正则对象/日期对象,结果都是 "object",这样的话,我们就不能通过 typeof 区分这个值是普通对象还是数组对象。

上面说了这么多,现在有个变量 x,它的数据类型目前不确定,我们需要判断变量 x 是否为对象,如果是的话,就执行相关处理逻辑,要怎么做?

第一次尝试

if (typeof x === "object") {         
    console.log('这是一个对象');
}

上面这种方式,可以吗?有仔细看过上面的局限性说明的同学,应该都知道上面这个判断条件并不完善,因为 typeof null 的结果也是 "object"。

所以我们改成:

if (x !== null && typeof x == "object") {         
    console.log('这是一个对象');
}

instanceof

1、定义:检测当前实例是否属于这个类。底层机制:只要当前类出现在实例的原型链上,结果都是 true

2、语法:[example] instanceof [class],example 是实例,class 是类

3、返回值:属于返回 true,否则返回 false

4、优点:可以弥补 typeof 无法细分对象类型的局限性。比如,想要检测某个值,是否为数组,只要看返回值是否为 true 即可。

let arr = [];
console.log(arr instanceof Array); // => true
console.log(arr instanceof RegExp); // => false
console.log(arr instanceof Object); // => true

5、局限性

  1. 不能检测基本数据类型
  2. 基本数据类型的实例是无法基于它检测出来的
    • 字面量方式创建的不能检测
    • 构造函数创建的就可以检测
  3. 无法基于结果判断是否为普通对象,因为不管是数组对象还是正则对象等等,都是 Object 的实例,所以检测结果都是 true
  4. 我们可以肆意的修改原型的指向,所以检测出来的结果是不准的

下面分别说明下;

// 第 1 点
console.log(1 instanceof Array);  // => false

// 第 2 点
var str = '追梦玩家';
var str2 = new String('追梦玩家');
console.log(str instanceof String); // false
console.log(str2 instanceof String); // true

// 第 3 点
let obj = [];
console.log(obj instanceof Object); // => true

// 第 4 点
function Fn() {
  this.x = 100;
}
Fn.prototype = Object.create(Array.prototype);

let f = new Fn;
console.log(f, f instanceof Array); //  {x: 100} true

instanceof 检测机制:验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为 true

我们能不能自己实现一个 instanceof ?想这么多干啥呢?干就完事。

function instance_of(example, classFunc) {
  // example => 实例 classFunc => 类
  let classFuncPrototype = classFunc.prototype,
      proto = Object.getPrototypeOf(example); 
  // example.__proto__ IE 不支持

  while(true) {
    if(proto === null) {
      // Object.prototype.__proto__ => null
      return false;
    }
    if(proto === classFuncPrototype) {
      // 查找过程中发现有,则证明实例是这个类的一个实例
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }
}
// 实例.__proto__ === 类.prototype
let arr = [];
console.log(instance_of(arr, Array)); // => true
console.log(instance_of(arr, RegExp)); // => false
console.log(instance_of(arr, Object)); // => true

constructor

1、定义:判断当前实例的constructor的属性值是不是预估的类

2、语法:[example].constructor===[class]

3、返回值:属于返回 true,否则返回 false

4、优点:

  • 用起来看似比 instanceof 还好用一点,可以检测基本数据类型
  • 实例.constructor 一般都等于 类.prototype.constructor 也就是当前类本身。前提是 constructor 没有被修改

5、局限性

  1. 不能够给当前类的原型进行重定向,会造成检测的结果不准确
  2. 不能够给当前实例增加私有属性 constructor,也会造成检测的结果不准确
  3. 非常容易被修改,因为 JS 中的 constructor 是不被保护,是可以随便修改,这样基于 constructor 检测的值存在不确定性,虽然平时也不用它来检测数据类型。
// 第 1 点
Number.prototype.constructor = 'AA'; // 加上这个之后,就变成 false
let n = 1;
console.log(n.constructor === Number); // false

// 第 2 点
let arr = [];
arr.constructor = Object; // 执行这个diam,下面的结果就变成了 false
console.log(arr.constructor === Array); // false

Object.prototype.toString.call

上面介绍的数据类型检测方法都有一定的不足,这里我们介绍的 Object.prototype.toString.call 方法可以很好的判断数据类型,并且弥补之前几种方法出现的问题、

每一个引用类型都有toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

const obj = {};
obj.toString() // "[object Object]"

值得注意的是,上面提到了如果此方法在自定义对象中未被覆盖,toString 才会达到预想的效果,事实上,大部分引用类型比如 Array、Date、RegExp 等都重写了 toString 方法。

所以我们通过直接调用 Object 原型上未被覆盖的 toString 方法,使用 call 来改变 this 的指向来打包我们想要的效果。

1、定义:找到 Object.prototype 上的 toString 方法,然后调用这个方法,并且基于 call 让方法中的 this 指向检测的数据值,这样就可以实现数据类型检测。

2、语法:

  • Object.prototype.toString.call([value])
  • ({}).toString.call([value])

3、返回值:返回一个表示该对象的字符串,“[Object 当前被检测实例所属的类]”,比如,"[object Object]"

返回结果汇总:"[object Number/String/Boolean/Null/Undefined/Symbol/Object/Array/RegExp/Date/Function]"

4、优点:

  • 专用用来检测数据类型的方法,正规军,基本上不存在局限性的数据类型检测方法
  • 基于它可以有效检测任何数据类型的值

5、局限性

  1. 只能检测内置类,不能检测自定义类
  2. 只要是自定义类返回的都是 "[Object Object]"

此方法是基于 JS 本身专门进行数据检测的,所以是目前检测数据类型比较好的方法

封装实现 jQuey 检测数据类型的方法

真实项目当中,如果想封装万能的检测数据类型的方法,我们应该把 typeof 和 toString 结合一起使用。参考 jQuery 源码,封装一个检测数据类型的方法。

原理:

  1. 设定数据类型的映射表
  2. 如果是基本数据类型值,直接使用 typeof 检测。如果是引用类型,则使用 Object.prototype.call ,去检测类型
  3. 引用数据类型,从 class2type 这个对象,基于对应的属性名,找到属性值,进行返回
(function () {
  var class2type = {};
  var toString = class2type.toString; // => Object.prototype.toString

  // 设定数据类型的映射表
  ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error"].forEach(name => {
    class2type[`[object ${name}]`] = name.toLowerCase();
  });
  // console.log(class2type);
  function toType(obj) {
    if (obj == null) {
      // 传递的值是 null 或者 undefined,就返回对应的字符串
      return obj + "";
    }
    // 基本数据类型都采用 typeof 检测 
    return typeof obj === "object" || typeof obj === "function" ?
      class2type[toString.call(obj)] || "object" :
      typeof obj;
  }
  window.toType = toType;
})();

console.log(toType(1)); //=>"number"
console.log(toType(NaN)); //=>"number"
console.log(toType([])); //=>"array"
console.log(toType(/^\d+$/)); //=>"regexp"
console.log(toType({})); //=>"object"
console.log(toType(null)); //=> "null"
console.log(toType()); //=>"undefined"

题外话:如何准确的判断数据类型?

可以使用 Array.isArray。

Array.isArray 是构造函数 Array 上的一个方法,它可以判断一个值的类型是否为数组

Array.isArray([1,2,3]); // true
Array.isArray(1); // false
Array.isArray('abc'); // false

当然,该方法只能用于判断数组,并不能判断是否为其他数据类型

参考

总结

JavaScript中的类型检测方法各有特点,对于其用法我们做一下小结:

  • 基本数据类型可以使用typeof来判断,语法简单易用
  • instanceofconstructor可以用来检测对象的具体类型,但是在修改了原型链后,结果会不准确
  • Object.prototype.toString.call是一个万能公式,基本上可以用来检测JavaScript中所有的数据类型
  • 可以直接使用Array.isArray来准确检测数组

文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你或者喜欢,欢迎点赞和关注。