"探索JavaScript中的类型判断:深入了解数据类型与类型转换"

289 阅读6分钟

类型判断在JS中大体有四种方法,这里话不多说直接上干货。

案例引用

function add(a,b) {

    return a+b
}
add( '1', 2 )

在这里你要返回a与b的和,而在传入实参的过程中,一个是字符串,一个数字类型,类型不同的加和,返回值是什么类型?运算结果输出就有误。这里就要将传入给形参的值进行判断统一类型进行计算:

function add(a, b) {
  if (typeof(a) ===  'number' && typeof b === 'number') {
    return a + b;
  }
  console.log('参数类型不对');
}
console.log(add(1, 2));
console.lod(add('1',2))

用typeof 方法进行一个if类型判断,如果a和b的值都是number类型执行加和操作,否则返回类型不同。

image.png

所以之后的学习中要学会类型的判断,才能计算出相应的问题,接下来详细的介绍下类型的判断方法:

类型判断

1. typeof

在JS中,原始类型有七大类:

let s = '123'

let n = 123

let f = true

let u = undefined

let nu = null

let sy = Symbol(123) // Symbol

let big = 1234n // BigInt

分别是字符串,数字,布尔值,undefined,null,Symbol和BigInt,除此之外还有一些对象:

let obj = {}

let arr = [ ]

let fn = function() {}

let date = new Date()

空对象,数组,函数体,Date...

let s = '123'   // string
let n = 123    // number
let f = true   // boolean
let u = undefined   // undefined
let nu = null    // null
let sy = Symbol(123)   // Symbol
let big = 1234n    // BigInt


let obj = {}
let arr = []
let fn = function () { }
let date = new Date()


console.log(typeof(s));  
console.log(typeof(n)); 
console.log(typeof(f));  
console.log(typeof(u));  
console.log(typeof(sy));  
console.log(typeof(big));  

console.log(typeof(nu));  

console.log(typeof (obj));  
console.log(typeof (arr));  
console.log(typeof (date));  

console.log(typeof (fn));  

分别执行出结果看看typeof的判断效果:

image.png

这里其他的原始类型都判断没有问题,而null确是object,所以type可以判断除 null 之外的所有原始类型。

image.png

而在对象中 typeof判断除了 function 其他所有的引用类型都会被判断成 obiect,这里原理是什么:

typeof进行判断时,typeof 是通过将值转换为二进制后判断其二进制前三位是否为0,是则为 object,所以引用类型二进制的前三位是000,function判断成function记住就好,而所有原始类型前三位是111,这里null确实存在bug,当年被打造出来转换成二进制全是0,毫无疑问判断成object,现如今存在多种因素目前无法更改。

function isObject(o) {
    if (typeof (o) === 'object' && o !== null) {
        return true
    }
    return false
}

let res = isObject({ a: 1 })
console.log(res); 

所以我们这里用typeof进行是不是对象的判断,强调一点,null这个记得去除。

所以typeof:

  1. 可以判断除 null 之外的所有原始类型
  2. 判断除了 function 其他所有的引用类型都会被判断成 obiect
  3. typeof 是通过将值转换为二进制后判断其二进制前三位是否为0,是则为 object

2. instanceof

let s = '123'   // string
let n = 123    // number
let f = true   // boolean
let u = undefined   // undefined
let nu = null    // null
let sy = Symbol(123)   // Symbol
let big = 1234n    // BigInt


let obj = {}
let arr = []
let fn = function () { }
let date = new Date()

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

console.log(s instanceof String);
console.log(n instanceof Number);

instanceof的用法是:x instanceof 某种数据类型,返回true和false。

image.png

根据运行结果你会发现,引用类型可以判断,而原始类型却不能判断,也就是说:instanceof只能判断引用类型。

image.png

但是在这里你会发现,将数组判断成对象返回的也是true。

这里的关系就是原型之间,一个实例对象的隐式原型会等于构造这个函数的显示原型:

arr.__proto__ = Array.prototype

Array.prototype.__proto__ = Object.prototype

这里原型不太明白的可以参考我的文章:原型

instanceof的构建机制是什么?这里构建一个myinstanceof来阐述一下:

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

}
console.log(myinstanceof([], Array));  // true
console.log(myinstanceof([], Object));  // true
console.log(myinstanceof({}, Array));  // false

我们通过L的隐式原型等于R的显示原型来返回true 值,第一层L的隐式原型没有,那就让L的隐式运行一直构建多层迭代直到到达R的显示原型,若是没有,返回false,这里记住排除null,因为没有隐式原型,所以instanceof为什么不能判断原始数据类型,其根本就是没有原型。

所以instanceof:

  1. 只能判断引用类型
  2. 通过原型链查找来判断类型

3. Object.prototype.toString.call(xxx)

这里有 官方文档 大家可以多去了解。

先看这个方法的实现:

image.png

后面就判断除了这里的类型。先介绍一下Object.prototype.toString,在上面的官方文档中,有这样一段:

image.png

这里翻译过来就是这样:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. 将 O 作为 ToObject(this) 的执行结果
  4. 定义一个class作为内部属性 [[class]] 的值
  5. 返回由 "[object" 和 class 和 "]" 组成的字符串

把这里记住就行,理解成一个概念就好。在这里 Object.prototype.toString(a)ToObject(a)你把你把任何一个值传进来,它都会把他转换成对象。

image.png

所以这里也就是为什么会对象会转换成这样字符串:

屏幕截图 2024-05-17 122506.png

但在这里仍不能判断数据类型,加上call才能判断

image.png

所以call的原理在哪里?就是用call将toString里面的值强行摆正,看个例子:

var obj = {
    a: 1
}

function foo() {
    console.log(this.a);
}

foo.call(a)

这里call将this里面的指向指到 obj 中,这样才能访问到 obj 中 a 的值,关于this的指向不太明白的也可以访问我的文章:this了解指向问题。

对于call的理解,这里创建一个mycall来演示call的运行机制:

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  // 让对象拥有该函数  context={Symbol('key'): foo}
    context[fn]()  // 触发了隐式绑定
    delete context[fn]

}
foo.mycall(obj)
console.log(obj);

通俗的来讲,就是让在调用的对象中创建该函数,再调用这个函数(让其触发隐式绑定),最后在删除该函数

  1. context [fn] = this // 让对象拥有该函数 context={Symbol('key'): foo}

  2. context [fn] () // 触发了隐式绑定

  3. delete context [fn]

至于整段代码来说,在函数体中就行调用,判断调用mycall的是不是函数体,返回一个值,再将this中的context创建出对象Key值为函数体this,最后执行上面三部的操作。说到底,这里就是将foo的方法借到obj中去用,再删除,这就是call的运行原理。

这里还有一个疑问,对于 foo.mycall(obj)中如何在obj中传入参数?可以自行手操一下,关注我,后面来进行解答。

所以为什么Object.prototype.toString.call(xxx)可以判断一个变量的类型?

在这个方法中 Object.prototype.toString.call(xxx),是call将()里面的值让toString进行调用即: xxx.toString的调用,由于其调用导致无论如何某某的值都会是对象,最后来判断出对象的类型。

4. Array.isArray(x)

这只能判断数组,数组返回true 否则返回false 。

总结:

类型判断的四种方法可以搞清楚对应优点,找出特例,这会在之后的学习中事半功倍。分享结束,感谢支持!

image.png