js四大常用类型数据判断(内含面试热点!!)

250 阅读9分钟

今天,小编学习了js中的四种类型判断以及call源码的底层逻辑,深有所获,来给大家分享一下:

在js中有四种常用的方法可以判断数据类型,分别为typeofinstanceofObject.prototype.toString.call()Array.isArray()

在进行判断数据类型之前,小编带大家来回顾一下数据有哪些类型: 在js中,数据类型可以分为两大类,原始数据类型和引用数据类型

原始数据类型有七种:数值Number 、字符串String、布尔值Boolean、Null、Undefined、Symbol、BigInt

引用数据类型:对象Object、函数Function、数组Array、日期Date等

分别定义上述所有数据类型:

// 原始数据类型
let s = '123';
let n = 123;
let f = true;
let u = undefined;
let nul = null;
let sy = Symbol(123);
let b = BigInt(123);

// 引用数据类型
let obj = {};
let arr = [];
let fn = function () {};
let date = new Date();// date类型对象

一、typeof

1.typeof 可以判断除了 null之外的所有原始类型,

2.除了function 其他所有的引用类型都会被判断成object

3.typeof 是通过将值转化为二进制后判断其二进制前三位是否为0,是则为object

console.log(typeof(s));
console.log(typeof(n));
console.log(typeof(f));
console.log(typeof(u));
console.log(typeof(sy));
console.log(typeof(null));// object --> 二进制前三位为0

console.log(typeof(obj));// object
console.log(typeof(arr));// object
console.log(typeof(fn));
console.log(typeof(date));// object

输出:

Snipaste_2024-05-17_12-29-11.png

注:在JavaScript中,NaN 属于原始数据类型(primitive type)中的 number 类型。尽管 NaN 代表 "Not a Number",它实际上是 number 类型的一个特殊值,用于表示一个未定义或非法的数值结果。例如,当你尝试将一个非数字字符串转换为数字时,或者执行导致数学上无意义的操作时,结果就会是 NaN

console.log(typeof NaN); // 输出 "number"

还有null和undefined的判断

console.log(null==undefined)//true
console.log(null===undefined)//false

Es6之后新增的两个原始数据类型bigint和symbol对于typeof来说同样可以来判断,这里小编也来给大家介绍一下这两种类型:

bigint :

BigInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。在JavaScript中,传统的Number类型在处理非常大或非常小的数字时可能会遇到精度丢失的问题,而BigInt类型就是为了解决这个问题而引入的。

在js中书写次方是两个乘号,也就是 2 ** 53 再大就需要用上bigint了,我们只需要在数字后面加个n即可,即let big = 223n

Symbol:

Symbol用于定义独一无二的值,通常不参与逻辑运算中

let s1 = Symbol('hello')
let s2 = Symbol('hello')
console.log(s1 === s2) // false

可能有人会问到== 和 === 的区别,我给大家说一下:

== 和 ===

== 是用来判断值是否相等,===是用来判断值和数据类型是否相同

console.log(1 == '1') // true 这里是因为数据类型转换,比较值时字符串会被转为数字
console.log(1 === '1') // false

在引用数据类型中===还需要比上一个指针,也就是地址

二、instanceof

1.只能判断引用类型,不能判断原始类型

//原始类型没有原型,所以instanceof无法判断,只有对象和函数有原型

2.通过原型链的查找来判断类型

console.log(obj instanceof Object);
console.log(arr instanceof Array);
console.log(fn instanceof Function);
console.log(date instanceof Date);

image-20240517153613052.png

console.log(s instanceof String);// false

image-20240517153712968.png

instanceof确实可以判断引用类型,那它能不能将arr和fn判断为是否是对象呢?咱们试试看

console.log(arr instanceof Object); // true
console.log(fn instanceof Object); // true

好像也没问题,数组和函数确实都是一种特殊的对象,那么instanceof到底是怎么判断的呢?其实这里是因为instanceof是通过原型链来判断的。有不理解原型和原型链的小伙伴可以先看看我之前的这篇文章:

干货分享——一文带你搞懂原型与原型链

arr是个空数组,也就相当于new Array(),所以arr是一个实例对象。a instanceof b在判断的时候,会先判断a的隐式原型是否等于b的显示原型,如果不等于的话a就会继续沿着原型链深入判断。就像上面这个,arr的隐式原型就是构造函数Array的显示原型,然后构造函数Array也是个对象,他被Object实例出来的,所以构造函数Array的隐式原型就是Ojbect的显示原型

所以arr instanceof Object的查找原理其实是这样的:

arr.__proto__ 是否为 Object.prototype // 否
arr.__proto__.__proto__是否为Object.prototype // 是 返回true

a instanceof b的a最终会沿着__proto__一直找到object的null值(object.__proto__ == null

在这里就很容易有面试题了。面试官很可能就会叫你手写一个instanceof: 那么该如何去写呢?

不慌,既然咱们都已经知道原理了,那不是简简单单嘛!

1.首先我们要知道arr.__proto__ = Array.prototype;Array.prototype.__proto__ = Object.prototype;

2.一直沿着原型链往上找原型,直到满足=L.__proto__ === R.prototype,同时将L.__proto__赋值给L

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

判断左边的隐式原型是否为右边的显示原型,如果不是,则判断左边的隐式原型的隐式原型是否为右边的显示原型,如此循环下去,如果左边到null了,就返回false,a也可能自己创造隐式原型,因此要用while循环进行判断。

这里很容易作为面试题被面试官问到,你回答的时候就可以这样说:

A instanceof b就是a通过循环不断找a的隐式原型是否为b的显示原型,找到了就会返回true,找不到最终到一个null,就返回false

三、Object.prototype.toString.call()

相比前几种这是js中最完美的判断数据类型的方法了,但是它的原理可是有点不好理解的。

下面是官方文档给出的解释:es5.github.io/#x15.2.4.2

image-20240517163741685.png 当toString这个方法被调用时,会有以下五个步骤:

1.如果this的值是undefined,那么返回"[object Undefined]"

2.如果this的值是null,那么返回"[object Null]"

3.将 O 作为ToObject(this)的执行结果

4.定义一个class作为内部属性[[class]]的值

5.返回由“[object”、“class”、“]”组成的字符串,其中“class”是内部属性[[class]]的值

咱们直接来看看它的效果吧:

// 7种原始数据类型都能识别
console.log(Object.prototype.toString.call(123)) // '[object Number]'
console.log(Object.prototype.toString.call('Hello') )// '[object String]'
console.log(Object.prototype.toString.call(true)) // '[object Boolean]'
console.log(Object.prototype.toString.call(undefined) )// '[object Undefined]'
console.log(Object.prototype.toString.call(null)) // '[object Null]'
console.log(Object.prototype.toString.call(123n)) // '[object BigInt]'
console.log(Object.prototype.toString.call(Symbol('hello'))) // '[object Symbol]'

// 四种引用类型也都可以识别
console.log(Object.prototype.toString.call({})) // '[object Object]'
console.log(Object.prototype.toString.call([]) )// '[object Array]'
console.log(Object.prototype.toString.call(function(){})) // '[object Function]'
console.log(Object.prototype.toString.call(new Date()) )// '[object Date]'

image-20240517173528953.png 绝!试完我是真觉得牛逼!这方法也太强了吧,前面提到的几种方法都有缺点,这个方法全给你判断出来了,甚至null这个bug这没问题!

不知道大家发现没有,为什么官方给的方法是Object.prototype.toString.(),而不是Object.prototype.toString.call()呢?那么如果我们不用call行的通嘛?

来试试:

Object.prototype.toString(123) // '[object Object]'

你会发现行不通,不行,123这个东西调用不了左边部分的方法,不管咱们输入什么类型进去都是这个输出,必须通过call这个东西来让123调用toString方法。

其实就是利用了Object.prototype.toString方法,并通过.call()方法改变其内部的this绑定,使其能够应用于任何对象,从而判断该对象的具体类型,关于call在我之前的文章中也讲过call的作用

葵花宝典——this指向问题

说到这里其实又可以让你手写一个call()方法的源码,用来改变函数的执行上下文,也就是函数内部this的值。该如何实现呢?

1.类型检查: 首先,通过typeof this !== 'function'检查调用mycall方法的主体(即this指向的对象)是否为函数类型。如果不是函数,则返回出一个类型错误(TypeError),提示调用者这不是一个函数。

2.创建唯一标识符: 使用Symbol创建一个独一无二的属性名fn。这样做可以防止与对象中已存在的属性名冲突,保证临时添加的函数不会覆盖原有的属性。

3.将函数绑定到目标对象: 将当前函数(this,即想要改变调用上下文的函数)赋值给context对象上的fn属性。这样,context对象就暂时拥有了这个函数,可以通过context[fn]来调用它。

4.调用函数并利用隐式绑定: 接下来,通过context[fn]()执行这个函数。由于是在context对象上调用的,根据JavaScript的函数调用规则,这里的this会被绑定到context上,实现了上下文的改变。

清理: 最后,通过delete context[fn]删除之前添加到context对象上的临时函数,以保持原对象的清洁。

Function.prototype.mycall = function(context) {
    // 调用的哥们是不是函数体
    if (typeof this !== 'function' ) {
        return new TypeError(this + 'is not a function');
    }

    // this里面的this => context
    const fn = Symbol('key');
    context[fn] = this;// 让对象拥有该函数 {Symbol('key'): foo}
    context[fn]();// 触发了隐式绑定
    delete context[fn];
}

应用它

var obj = {
    a:1
}
function foo() {
    console.log(this.a);
}
foo.mycall(obj);//输出1

确实可以,是同样的效果。

所以当面试官问到你Object.prototype.toString.call()为什么可以准确判断数据类型,或者说问你这个方法有了call为何就不一样,你只需要把call的源码逻辑告诉他就可以,其实就是一个隐式绑定。

比如**Object.prototype.toString.call(num)**你可以理解为call把左边的toString方法借给右边的num用了,然后让num去执行toString方法,也就是识别出数据类型,toString本身是不接受参数的,call把toString借到了num身上并且去调用。call把左边的this指到右边去了。

四、Array.isArray

判断某个变量是否为数组类型的一个内置函数

let a = [];
let b = {};
let c = 123;
console.log(Array.isArray(a));
console.log(Array.isArray(b));
console.log(Array.isArray(c));

image-20240517181302043.png

总结:

typeof是用来判断原始数据类型的(除null这个bug外),引用类型只能判断一个函数。instanceof只能判断引用类型,但是判断数组,函数不准,因为他是通过原型链来判断的。Object.prototype.toString.call(***)可以判断所有的数据类型,call的作用就是把左边toString方法借给了右边,让右边的东西去调用toString方法。Array.isArray()Array自带的方法,只能判断数组。

如果觉得本文对你有帮助的话可以点个免费的赞嘛!感谢感谢!