解锁JavaScript类型判断的神秘技能,成为编程界的福尔摩斯!

390 阅读7分钟

写代码就像搭积木,而类型判断就是确保每块积木都能稳稳搭上去的关键。在JavaScript的世界里,数据类型多种多样,有时候我们得搞清楚这些数据的“身份”,才能让程序正常运行。类型判断就像是给数据“验明正身”的过程,它帮助我们确保数据是按照我们预期的方式被处理的。掌握JavaScript的类型判断,能让你的代码更加健壮,减少出错的可能性。接下来,我们就来一起探索JavaScript的类型判断,看看它是如何帮助我们的代码更加可靠和高效的。

数据类型

在学习判断类型之前,我们先回忆一下有哪些数据类型

原始类型

Number
String
boolean
undefined
null
bigInt
Symbol

引用类型

function
Object
Array
Date

类型判断

实例

function add(x,y){
        return x+y;
}
console.log(add(2,'3'));

像上面的代码,如果我们是想求两个数字的和,但这时候传进去的有数字字符串,我们还能得到两数之和吗?看看代码执行结果

74.png 执行结果字体是白色的,说明是字符串,与我们想要的效果不一样,因此如果用户传入的是字符串,我们要先将字符串变为number类型,所以在函数里,我们要进行一个判断操作

function add(x,y){
    if(typeof(x)=='number'&&typeof(y) =='number'){
        return x+y;
    }
   else{
    return Number(x)+Number(y);
   }
}
console.log(add(2,'3'));

typeof来判断是不是都为number类型,如果不是就将x,y都转换为number类型,这样就能得到我们想要的两数相加的效果了 75.png

typeof

let a = 1
// console.log(typeof a);  //Number
a = 'hello'
// console.log(typeof a);  //String
a = true
// console.log(typeof a);  //boolean
a = null
// console.log(typeof a);  //object
a = undefined
// console.log(typeof a);  //undefined
a = Symbol(1)
// console.log(typeof a);  //symbol
a = 123n
// console.log(typeof a);  //bigint
a = []
// console.log(typeof a);  // object
a = {}
// console.log(typeof a);  //object
a = function () {}
// console.log(typeof a);  // function
a = new Date()
// console.log(typeof a);  //object

可以看得出来,typeof可以判断除了null以外的所有原始类型,为什么不能判断null,在这篇文章中有解释(juejin.cn/post/744071…) 而引用类型除了函数以外都会判断为object,所以这里对象类型判断准确可以说它是蒙的,因为它会将引用类型无脑判断为object,因此可以看得出来,typeof 可以准确判断除了null之外的所有原始类型,不能判断引用类型(除了function),function有很多功能,做了特殊处理。那typeof只能判断原始类型,我们想判断引用类型怎么办呢官方有没有打造一个判断引用类型的方法呢,当然是有的,叫instanceof

instanceof

instancef的使用

console.log({} instanceof Object);

console.log([] instanceof Array);

console.log(function () { } instanceof Function);

console.log(new Date() instanceof Date);

console.log('String' instanceof String);

console.log(123 instanceof Number);

console.log(true instanceof Boolean);

看上面的代码,我们用instanceof来判断类型,instanceof翻译的意思就是‘隶属于’的意思,左边放值,右边放类型,然后让我们来看看它的输出结果

76.png 可以看到引用类型可以成功判断,但是原始类型无法进行判断,那看看之前那个bug-null能不能成功判断呢

console.log(null instanceof Null)

77.png 可以看到报错了,所以是无法判断null的类型

那我们将引用类型后面全部放object会怎么样

console.log({} instanceof Object);

console.log([] instanceof Object);

console.log(function () { } instanceof Object);

console.log(new Date() instanceof Object);

78.png 可以看到,还是都为true,不过我们也无法反驳,因为引用类型确实也可以说它是对象。那我们要来了解一下instanceof的判断原理

instanceof的判断原理

function Bus() { }
let bus = new Bus()
console.log(bus instanceof Bus);

我们看上面的代码,我们创建一个函数Bus然后创建出它的一个实例对象,再用instanceof去判断这个实例对象是否属于这个函数。

79.png

可以看到它能判断出这个实例对象是否由构造函数Bus产生的 那我们再创建一个car函数

function car(){}
Bus.prototype=new car();
function Bus() { }
let bus = new Bus()
console.log(bus instanceof car);

那现在来判断bus是不是属于car,我们来看看执行结果

80.png

可以看到,依旧是属于的,那为什么呢,首先我们知道实例对象bus的隐式原型等于创建它的构造函数Bus的显示原型,所以我们可以判断出bus属于Bus,那为什么bus还会判断出属于car, bus.__ proto__等于car.prototype吗, 当然不是但是Bus.prototype等于car创建出来的实例对象,Bus.prototype.__ proto __ 等于car.prototype,而 bus. __ proto __ =Bus.prototype,所以bus. __ proto __ . __ proto __ 等于car.prototype,因此bus也是属于car的,就和翻族谱一样,那作为第一页的object 呢,bus会属于它吗,我们来看看

console.log(bus instanceof Object);

81.png 可以看到bus也是属于Object的,那我们可以知道instanceof可以判断值的类型,而且是通过原型链来判断的,不懂原型链的小伙伴可以先看看这篇文章(juejin.cn/post/743932…)

手写instanceof

function myinstanceof(L,R){
   
    while(L!=null){
        L = L.__proto__
        if(L===R.prototype){
            return true;
        }
    }
    return false;
    
}

我们打造一个函数myinstanceof,然后我们传入对象L和构造函数,接着我们来进行判断,如果对象的隐式原型等于构造函数的显示原型,那么就返回true,不相等则进行循环,L为构造函数Object的隐式原型,Object的隐式原型没有东西,因此当为bull的时候,循环结束,返回false

让我们来使用一下

82.png

function myinstanceof(L,R){
   
    while(L!=null){
        L = L.__proto__
        if(L===R.prototype){
            return true;
        }
    }
    return false;
    
}
console.log(myinstanceof([],Array));
console.log(myinstanceof({},Array));
console.log(myinstanceof({},Object));

可以看到,能够成功判断出值的类型

我们知道是通过原型链进行判断的,那么我们就能知道为什么instanceof不能判断原始类型了,因为原始类型不具备属性和方法,没有隐式原型。

typeof只能判断原始类型,用instanceof又只能判断引用类型,那有没有一种方法可以同时判断原始类型和引用类型呢,确实有

Object.pritotype.toString.call(x)

这个方法就是拿Object身上的原型上的toString出来用,call的作用是将toString上的this指向x并调用toString,其实就是把toString借给了x用,如果不了解,可以看看这篇文章里的介绍 (juejin.cn/post/744007…) 然后让我们看看能否达到我们想使用的效果

let a = 1
let b = {}
let c = function () {

}
console.log(Object.prototype.toString.call(a));
console.log(Object.prototype.toString.call(b));
console.log(Object.prototype.toString.call(c));

83.png 可以看到,它成功判断出来引用类型和原始类型,但是为什么用toString方法会把它们转换成这么奇怪的样子呢,一个中括号里面放个Object和数据类型

这我们就要来看看官方文档了

84.png 这个文档表示的是当 Object.pritotype.toString.call()被执行的时候会干的几部操作,如果是undefined的话,那就直接返回[object Undefined],如果是null,那就返回[object Null]

其它的,设0为调用To0bject 的结果,将this 值作为参数传递ToObject(this)class为0的[[Class]]内部属性的值。[[Class]]是js内部的属性,只有v8能用,可以直接读取变量的类型,最后返回由“[object ”、class 和 “]” 三块拼接的结果

所以Object.prototype.toString.call(x) 借助Object原型上的toString方法在执行过程中会读取 x 的内部属性[[calss]]这一机制 ,还有一个专门判断数组的方法-Array.isArray(),也是用了[[calss]]这一机制,因为写死了,没有什么道理可讲,我们知道就好了

总结

JavaScript中,类型判断是确保数据按预期处理的关键。typeof运算符能准确判断除null外的所有原始类型,但对引用类型(除function)的判断不够精确。instanceof通过原型链判断引用类型,却无法处理原始类型。为了同时判断原始类型和引用类型,可以使用Object.prototype.toString.call()方法,它借助内部属性[[Class]]返回准确的类型标签。此外,Array.isArray()是专门用于判断数组的方法,也利用了这一机制。掌握这些类型判断方法,能让你的JavaScript代码更加健壮和高效。