我发现了一个JavaScript中的一个Bug——详解JavaScript中的数据类型。

326 阅读7分钟

先提个问:JavaScript中有多少种数据类型?emmm,有人说这还不简单啊,不就是Number类型,String类型,Boolean类型,object类型······说着说着就不知道自己说了哪些。

其实JavaScript中的数据类型主要分为两大类

第一种就是原始数据类型

原始数据类型又分为7种,最后两种很少用,所以我们只记住前面五种就可以了。

Number:表示整数和浮点数,例如 42, 3.14。

String:字符串类型,例如 "Hello, World!"。

Boolean:布尔类型,只有 true 和 false 两个值。

Undefined:这个类型表示一个变量被定义了但是并没有被赋值

Null:表示一个刻意的空值或者缺少值,代表变量或者对象属性被设置为“无值”或者“空”的状态。

Symbol(ES6 新增):符号类型,是唯一且不可变的值,常用于对象的键,以避免键名的冲突。

BigInt(ES10 新增):可以安全地存储、操作大整数,超过 Number.MAX_SAFE_INTEGER 的整数。

第二种是引用数据类型

引用数据类型我们常用的就是object,Function,Array,Date,还有我们常用于字符串匹配的正则(RegExp)。在es6中又新增了一些,但是我个人感觉有点多了,那些新加引用数据类型的我们更多的是平时的使用,例如es6中新增的Class,Promise,Error等。

Object:对象类型,可以包含多个键值对,如 let obj = {a:1,b:2}

Function:函数本身也是一种对象,可以作为值来传递和赋值。let foo = function(){}

Array:数组,用于存储有序的元素集合。let arr = [1,2,3,4]

Date:通常用于处理日期和时间。

RegExp:正则表达式对象,用于匹配字符串中的模式。

这些类型之所以被称为引用类型,是因为这些数据类型的数据当量很大,但是调用栈的空间又很小,不便于存储这些数据类型,于是便把它们存放在堆结构中国。当我们给它们赋值给变量或者作为参数传递给函数时,实际上传递的是这些数据结构在内存中的引用(地址)。

将JavaScript中的数据类型大致分为原始数据类型和引用数据类型,然后再记原始数据类型有哪些,引用数据类型有哪些,是不是就不容易讲着讲着就讲混了!!!

如何能判断各种数据类型呢?

我们常用的typeof方法来判断一个数据的类型

我相信typeof是很多大佬经常用的一种判断数据类型的方法,我也经常使用typeof这种方法来判断数据的类型,但是后来我才知道,原来typeof判断的数据类型并不准确!请看下面示例:

   console.log(typeof 42);     
   console.log(typeof "Hello");
   console.log(typeof true);   
   console.log(typeof undefined);
   console.log(typeof null); 

   console.log(typeof {});       
   console.log(typeof []);       
   console.log(typeof function(){}); 

正确的结果应该是number,string,boolean,undefined,null,object,array,function 但是实际的结果是什么呢?

image.png

嗯哼,初看前面几条貌似都是对的,可是一到后面,null怎么就变成了object呢,数组的类型也是object。聊到这里就不得不了解一下计算机底层的东西了。

在我们都知道计算机是以二进制来存储数据的,当要判断一个数据的数据类型时,计算机又会将数据转成二进制再来判断它的数据类型,而NUll转换为二进制就是000000000,而复杂的数据类型转成二进制后前三位都是0,而typeof判断数据类型就只看它的前三位,如果前三位全是0的话,那么就返回object。这也就解释了为什么会判断object,array,以及null的时候会给出object的结果了。所以不光是我认为,所有人都认为将null判断为object是JavaScript中的一个小BUG

那为什么函数就能够正确的判断出他是function呢?因为typeof对函数的这种特例处理确保了它能准确地区分出函数类型。

所以,通过上面的结果,我们可以总结出这样一句话来介绍typeof的作用:

typeof 可以准确判断出null以外的原始类型,而不能判断出function外的引用类型

那怎么判断引用数据类型呢?

在js中还有一个方法用来判断一个数据是否为引用数据类型,即instanceof,如果是引用数据类型则返回true,否则返回false。这个方法只能用来判断引用数据类型,不能判断原始数据类型,如果判断原始数据类型,只会返回false。

instanceof判断一个数据是否为引用数据类型是通过原型链的查找来判断

学过原型的大佬们都知道,一个实例对象的隐式原型等于他构造函数的显示原型。我们想判断一个数组是否为原始数据类型,就可以使用arr instanceof Array,这样就可以判断他是不是个数组类型,但是这样说也有点不够准确!!!

为什么这样说呢?因为大家看看这个这行代码!console.log(arr instanceof Object);,这时打印出来也是true。???这就是我们前面说的instanceof判断的原理,因为它是将这个数据的隐式原型 与构造函数的显示原型相比较。如果不相同再往上去找,直到找到object为止,如果还没找到就返回false。而arr.__proto__ = Array.prototypeArray.prototype.__proto__ = Object.prototype最后还是找到了,所以就返回true。

了解了instanceof的原理之后是不是觉得可以自己手搓一个instanceof

根据上面介绍的instanceof原理, 我们可以自己手搓一个myInstanceof,实现类似的效果。就是不断地比较隐式原型和构造函数的显示原型。

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

有没有一种近乎完美的办法可以判断所有的数据类型?

还真有一种完美的办法,它能够准确地识别并返回几乎所有的JavaScript数据类型的名称。这包括但不限于普通的对象(Object)、数组(Array)、函数(Function)、正则表达式(RegExp)、日期(Date)等。它就是 Object.prototype.toString()

我们首先先看一下它的效果:

image.png

Object.prototype.toString()是通过直接访问对象的内部[[Class]]属性,提供了最准确的类型信息。通过.call()方法确保方法作用于正确的对象,它能够返回如"[object Array]"这样精确的类型字符串。这种方法克服了typeof的局限性,能够准确区分所有内置类型,包括数组、正则表达式等。 因此,我们可以将Object.prototype.toString()方法的实现原理总结为五步:

  1. 如果 toString() 接受的值是 undefined则返回 [object Undefined]
  2. 如果 toString() 接受的值是 null则返回[object Null]
  3. 调用 ToObject(x) 将x转为对象,此时得到的对象内部一定拥有一个属性:[[Class]],该属性[[Class]]的值就是x的类型。
  4. 设,class 是[[Class]]的值
  5. 返回由"[","object ", "class", 和 "]"拼接组成的字符串,即字符串"[object class]"

懒鬼觉得老是这样写还是太麻烦了

我们还可以手动的给他封装一下,用一个type函数给他封装一下

function type(x) {
    return Object.prototype.toString.call(x).slice(8, -1);
}

console.log(type(5));
console.log(type("hello"));

这样就可以直接使用type判断数据的类型了,而且这样还可以返回我们想要的结果。

总结

我们在这里主要聊了下JavaScript中的数据类型,以及如何判断JavaScript中的数据类型。,通过一步步的深入,最后得到了我们想要得到的最优结果。其实JavaScript中有个方法 Array.isArray()能够判断是否为数组,但是也就仅仅只能判断是否为数组,得到的结果是ture or false