JavaScript的数据类型检测的四种方式详解 typeof instanceof constructor toString原理

JavaScript数据类型检测

对于确定一个js变量的类型,我们大概都知道几种方式。对于弱语言来说,检测其正确的类型是一件不是特别容易的事情。那么我们有几种方式来检查一个js变量的类型?

**从大的方向来说,js的数据类型分为基本数据类型和引用数据类型。**对于js的基本类型和引用类型是那些,这里就不在赘述。此外,在ES6新增的 symbol基本类型,ES7新增bigint大数类型,也是基本类型。

一般获取一个js类型的方式,大概应该是四种(我目前只知道这四种)

  1. typeof **
  2. instanceof
  3. toString -> Object.prototype.toString.call(变量) **
  4. constructor

注: ** 表示最常见的检测类型的方案

typeof底层原理

我们检测一个变量的类型,最常用的应该就是typeof运算符了。

typeof "你好"
//'string'
typeof 12
//'number'
typeof 12.3
//'number'
typeof 12n
//'bigint'
typeof Symbol()
//'symbol'
typeof null
//'object'
typeof undefined
//'undefined'
typeof []
//'object'
typeof {}
//'object'
typeof function(){}
//'function'
typeof new Object
//'object'
typeof new Boolean
//'object'
typeof new Set
//'object'

image-20220214214229556

结果很直观。对于基本数据类型,什么字符串啊,数字啊,都能直接判断出来其对应的类型,对于函数类型也可以直接判断,但是像内置对象(标准,非标准)等,其结果都是得到一个模糊的 ‘object’。很明显是不利于我们判断其更加准确的类型。而且,对于一个null,也就是空,其结果居然也是 ‘object’,其实是让人有些费解的。

首先不考虑那么多,要明白对象 typeof 变量,该求值得到的 结果一定是一个字符串,毋庸置疑。一个简单的面试题:

typeof typeof typeof "123" // ???

typeof在检查基本(原始值)类型时还是很好用的,但是到了对象不是很好用了。

typeof的优势就是使用起来简单,基本数据类型值基本上都可以有效检查

那么typeof底层是如何判断出其具体的类型?

其底层是按照二进制的方式来检查其类型的。[效率高]

我们知道计算机的底层都是一串串的二进制代码。所以以二进制的形式进行判断,其效率肯定会高一些。不要反驳:哪怕这种方式只是效率高那么一点点,也是效率提升了。

以64位二进制存储变量去具体值在堆(原始类型可以在栈中)中的地址:以下是以某些数字开头的变量存储的变量的类型

  • 二进制开头数字 类型

  • 000 对象

  • 1 整数

  • 010 浮点数

  • 100 字符串

  • 110 布尔

  • 000000…. null

  • -2^30 undefined

  • ……

可以看见对象的地址其二进制是以000开头的,而null变量的地址全是0,所以在判断到以000开头以后,就直接认为null也是个对象。

因而导致我们使用typeof判断null的类型的时候,得到的类型并不是 ‘null’,而是 ‘object’。

也要记住,函数也是对象,是一种特殊的对象

image-20220216204819473

如何判断一个变量的类型是否是对象?

function isObject(val){
    if(val === null) return false;
    return typeof val === 'object' || typeof val === 'function';
}

typeof 还有个特点:就是如果后面跟着的变量是未定义的,那么其得到的结果是 ‘undefined’,而不是报错

image-20220216205257363

instanceof方式

[实例] instanceof [类]:用来检测当前实例是否属于这个类。也可以认为,这个类是否出现在这个实例的原型上。

let arr = [];
console.log(arr instanceof Array);//true

优势:基于这种方式,可以弥补typeof无法细分引用类型的缺点。

弊端:

  1. arr是数组,但是arr也是一个对象

    console.log(arr instanceof Object)//true
    
  2. 无法通过检测结果来说明,arr是一个普通对象。因为数组对象,正则对象等,都是Object类的实例,而这些对象都不是普通对象。

  3. 只要我们检测的类出现在了实例的原型链上,结果都是true,所以通过继承,修改原型链等方式,都会对结果有影响

  4. instanceof检测的实例,必须都是引用数据类型的,它对基本类型值的操作无效

    console.log(10 instanceof Number);// false
    console.log(new Number(10) instanceof Number);//true
    

constructor

利用类和实例的关系,因为实例的constructor一般都等于类.property.constructor,也就是当前类本身,(前提是原型的constructor没有被破坏)

console.log([].constructor === Array)// true
const obj = {}
console.log(obj.constructor === Object);// true
console.log([].constructor === Object);// false
const num = 10
console.log(10.constructor === Number) ;// true

使用constructor来检测,有时候效果比instanceof更好,因为对于一个实例,其constructor属性都是确定唯一的,所以在没有肆意的胡乱修改的情况下,我们可以获取一个实例的真实类型,包括基本类型值都是可以获取到的。

**但是:constructor属性是很容易被修改的。**该属性是不被保护的。所以导致了这种检查方式并不保险。结果存在不确定性了。

不过没有人那么闲,去改内置类的constructor。

Object.property.toString.call([val])

JS中,唯一一种基本上不存在局限性的数据类型检测方式:Object.property.toString.call([val])

基于它,可以有效的检测任何数据类型的值。

const toString = Object.prototype.toString;
console.log(toString.call(10));
console.log(toString.call("10"));
console.log(toString.call(false));
console.log(toString.call(null));
console.log(toString.call(undefined));
console.log(toString.call(Symbol()));
console.log(toString.call(BigInt("10")));
console.log(toString.call({}));
console.log(toString.call([]));
console.log(toString.call(/\.js$/));
console.log(toString.call(function fn(){}));

image-20220407100840115

结果的结构:[object 真实类型(所属的构造函数)]

每一种数据类型构造函数的原型上都有toString方法。

  1. 每一种基本数据类型的原型上都有一个valueOf方法。
  2. Object的原型上也有valueOf方法
  3. 除了Object.property上的toString是用来返回当前实例所属类的信息(检测数据类型的),其余的都是转换为字符串的
const toString = Object.prototype.toString;
console.log((10).toString())//10
console.log("11".toString())//11
console.log((function () { }).toString())//function (){}
console.log(([1, 2, 3]).toString())//1,2,3
console.log(({}).toString()) // [object Object]

对象实例.toString():toString方法中的this是对象实例,也就是检测它的数据类型。也就是说this是谁,就是检测谁的数据类型。所以我们是把toString执行,基于call改变this指向要检测的值

const toString = Object.prototype.toString;
let num1 = 10, num2 = new Number(10);
let arr = [10, 20],
    obj = {
        name: "张三",
        age: 22
    }
// valueOf 方法 是获取原始值的 [[PrimitiveValue]]
// toString 是将获取到的原始值转为字符串
console.log(num1, num1.valueOf(), num1.toString())
console.log(num2, num2.valueOf(), num2.toString())

image-20220407114041186

引用类型

// 除了基本数据类型的包装类,其他的引用类型上都是没有valueOf方法的
// 所以我们对对象,数组等 获取valueOf方法都是获取到Object原型上的
// arr.toString 获取的是自己原型上的toString
console.log(arr, arr.valueOf(), arr.toString())
console.log(obj, obj.valueOf(), obj.toString())

image-20220407114651288

valueOf和toString的关系
  1. alert会把所有要输出的值转为字符串(隐式转换),像这种隐形转换为字符串的有很多

  2. 例如: 字符串拼接,把对象转换为数字(也是先转为字符串)…

  3. alert(num1) // num1.valueOf 获取原始值 [[PrimitiveValue]] -> [[PrimitiveValue]].toString() 转为字符串

// alert会把所有要输出的值转为字符串(隐式转换),像这种隐形转换为字符串的有很多
// 例如: 字符串拼接,把对象转换为数字(也是先转为字符串)...
// alert(num1) // num1.valueOf 获取原始值 -> 原始值.toString() 转为字符串
面试题

让a取什么值,可以保证下面的条件一定成立?

let a = ?;
if (a === 1 && a === 2 && a === 3) {
   console.log("ok")
}
方式一:普通对象
let a =
    // 方法1 a是一个对象 实现toString或者valueOf方法
    {
        n: 0,
        // 实现toString或者valueOf
        // toString() {
        //   return ++this.n
        // }
        valueOf() {
            return ++this.n
        }
    };
if (a == 1 && a == 2 && a == 3) {
    console.log("ok")
}
方式二:数组对象
// 方式2:实现valueOf方法 或者toString方法
a = [1, 2, 3];
a.valueOf = function(){
  return this.shift()
}
a.toString = function () {
   return this.shift()
}
if (a == 1 && a == 2 && a == 3) {
  console.log("ok")
}
方式三:某些数据类型包装类,引用类

例如:String,Number,Boolean,函数等

// a = new String();
// a = new Number();
// a = new Boolean();
// a = function(){};
a = new Number();
let value = 1;
a.valueOf = function () {
    return value++;
}
if (a == 1 && a == 2 && a == 3) {
    console.log("ok")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值