如何判断一个变量的类型

478 阅读4分钟

JS的数据类型有哪些

在js中数据类型分为两种:原始值和引用值。原始值有string、number、boolean、null、undefined、Symbol、BigInt等原始类型,引用值有Object引用类型,Date、Array、TypeArray等引用类型都是Object的实例。

JavaScript 中,原始值是不可变的——一旦创建了原始值,它就不能被改变,尽管持有它的变量可以被重新分配另一个值。相比之下,对象数组默认是可变的——它们的属性和元素可以在不重新分配新值的情况下更改。

由于以下几个原因,使用不可变的对象可能是有益的:

  • 提高性能(不计划将来更改对象)
  • 为了减少内存使用(进行对象引用 (en-US),而不是克隆整个对象)
  • 线程安全(多个线程可以引用同一对象,而不会相互干扰)
  • 降低开发人员的精神负担(对象的状态不会改变,其行为始终是一致的)

注意,你可以很容易证明可变性:只要对象提供一种方式来更改其属性,它就是可变的。另一方面,如果没有语言语义来保护它,就很难证明不可变性——这是一个开发人员约定俗成的问题。例如,Object.freeze() 是一种语言层面的方法,用于使对象在 JavaScript 中不可变。

如何区分JS的数据类型

除了 null之外,所有原始类型都可以使用 typeof 运算符测试。typeof null 返回 "object",因此必须使用 === null 来测试 null

引用值可以使用instanceof操作符判断,所有引用值都是Object的实例,因此通过instanceof操作符检测任何引用值和Object构造函数都会返回true。如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象。

基于上面的知识点,可以整理一下判断的步骤:

  1. 使用typeof操作符判断变量的类型,如果是string、number、boolean、undefined、Symbol、BigInt等数据类型,则直接返回数据类型的字符串。
  2. 如果数据类型判断是object,则再判断变量使用恒等于null,如果是则返回字符串null
  3. 如果不恒等于null则再使用instanceof操作符判断,依次判断Function、Date、Array、TypeArray、RegExp等引用类型字符串,如果是则返回对应的引用类型
  4. 如果不是则再次判断是否是Object的实例,如果是返回字符串object,如果不是则返回字符串unknown

代码实现:

function typeOf(param) {
  const originalValue = typeof param;
​
  if (!/object/i.test(originalValue)) {
    return originalValue;
  }
​
  if (param === null) {
    return 'null';
  }
​
  if (param instanceof Function) {
    return 'function';
  }
​
  if (param instanceof Array) {
    return 'array';
  }
​
  if (param instanceof Date) {
    return 'date';
  }
​
  if (param instanceof RegExp) {
    return 'regexp';
  }
​
  // 添加你需要判断的引用类型......
​
  if (param instanceof Object) {
    return 'object';
  }
​
  return 'unknown';
}
​
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
​
  setName(name) {
    this.name = name;
  }
}
​
const var1 = '';
const var2 = 1;
const var3 = true;
const var4 = null;
const var5 = undefined;
const var6 = Symbol(1);
const var7 = 1n;
const var8 = () => {};
const var9 = [1, 2, 3];
const var10 = new Date();
const var11 = /var/i;
const var12 = {};
const var13 = new Person();
​
[
  var1,
  var2,
  var3,
  var4,
  var5,
  var6,
  var7,
  var8,
  var9,
  var10,
  var11,
  var12,
  var13,
].forEach((item) => {
  console.log(typeOf(item));
});
​
// 打印结果
// string
// number
// boolean
// null
// undefined
// symbol
// bigint
// function
// array
// date
// regexp
// object
// object

优化实现

在上面的代码中发现一个问题,对于引用值需要明确知道其引用类型才能进行判断,否则不能返回其引用类型字符串,是否可以不知道其引用类型但是依然可以返回其引用的字符串?

答案是可以的,使用Object.prototype.toString方法可以返回[object XXX]字符串XXX就是数据类型。但是对于用户自定义的引用类型,Object.prototype.toString方法只会返回[object Object]字符串,而不是具体的引用类型,如果想要判断出具体的引用类型,可以参考Object.prototype.toString

优化后的代码:

function typeOf(param) {
  return Object.prototype.toString.call(param).slice(8, -1).toLowerCase();
}
​
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
​
  setName(name) {
    this.name = name;
  }
​
  toString() {
    return 'person';
  }
}
​
const var1 = '';
const var2 = 1;
const var3 = true;
const var4 = null;
const var5 = undefined;
const var6 = Symbol(1);
const var7 = 1n;
const var8 = () => {};
const var9 = [1, 2, 3];
const var10 = new Date();
const var11 = /var/i;
const var12 = {};
const var13 = new Person();
​
[
  var1,
  var2,
  var3,
  var4,
  var5,
  var6,
  var7,
  var8,
  var9,
  var10,
  var11,
  var12,
  var13,
].forEach((item) => {
  console.log(typeOf(item));
});
​
// 打印结果
// string
// number
// boolean
// null
// undefined
// symbol
// bigint
// function
// array
// date
// regexp
// object
// object

扩展知识

typeof null为什么是"object"?

null是一种基本数据类型,存储在栈区;Object是引用数据类型,存储在堆区。typeof null的结果是Object,但是null instanceof Object的结果是false,可以知道null并不是Object的实例,两者之间存在矛盾。

js在底层存储变量的时候,会在变量的机器码低位1~3位存储其类型信息:

机器码类型信息
000对象
010浮点数
100字符串
110布尔值
1整数

但对于undefinednull来说,这两个信息存储有些特殊的:

  • null:所有的机器码都是0
  • undefined:用-2^30整数表示

所以typof在判断null的时候就出现问题了,由于null所有机器均为0,因此直接被当做对象看待。