JS数据类型(分类,区别,判断)

TIP

关于数据类型方面的知识点很多,小弟主要从数据类型的分类区别类型判断三个角度来总结下,如有不当,还望路过的各位大佬给予指正!

分类

最新的 ECMAScript 标准定义了8种数据类型
数字(Number)字符串(String)布尔值(Boolean)undefinednull任意精度的整数 (BigInt)Symbol(ES6 新添加的一种类型)对象(Object) 。其中对象类型包括:数组(Array)函数(Function)、还有两个特殊的对象:正则(RegExp)日期(Date)

关于BigIntSymbol类型不太常用,因此我们在这里就不过多讨论了,下面通过几个不同角度对常用的6种数据类型进行下归类。

  • 按存储类型分类

    • 基本类型:数字(Number)字符串(String)布尔值(Boolean)undefinednull
    • 引用类型:对象(Object) 其中包括 Array、Function、RegExp、Date
  • 按是否可变分类

    • 可变类型:对象(Object) 其中包括 Array、Function、RegExp、Date
    • 不可变类型::数字(Number)字符串(String)布尔值(Boolean)undefinednull
  • 按是否可拥有方法分类

    • 可拥有方法类型:数字(Number)字符串(String)布尔值(Boolean)、对象(Object)
    • 不可拥有方法类型:null、undefined

基本类型和引用类型区别

  • 基本类型的值不可变,引用类型值可变
//基本数据类型
var a = 'abc';
var b= 123;
a = 'def';
b = 456;
console.log(a) //def
console.log(b) //456

可能我们会使用上面这个栗子来推翻基本数据类型的值不可变,但其实这里并不冲突,用 var a = 'abc' a = 'def'来说明, 首先声明一个变量a 并给其赋值abc, 这个时候在栈内存中会为a变量开辟一块空间来存储abc这个值,当将值更改为def的时候,会将之前变量a(栈内存中标识符a)之前对应的值整体给覆盖掉,然后存入新值,此时a的值就成了def。因此,分析后,我们会发现,对于基本数据类型,并不是对原始值的操作,而是将原始值覆盖后重新给变量赋新值。

//引用数据类型
 var cat = {};
 cat.name = 'Tom';
 cat.age = 12;
 console.log(cat) //{name: "Tom", age: 12}
 cat.name = 'candy'
 cat.age = '10'
 console.log(cat) //{name: "candy", age: 10}

对于引用类型,我们可以清晰的看到是可以通过操作指针的方式来修改存储在堆内存中的对象的属性值。

  • 基本类型不能添加属性和方法,引用类型可以添加属性和方法
//基本数据类型
var base = 'base';
base.attr = 'attr';
base.method = function(){//...};
console.log(base.attr); // undefined
console.log(base.method); // undefined

//引用数据类型
var cat = {};
cat.name = 'Tom';
cat.age = 10;
cat.sing = function(){console.log(cat.name + 'sing');} 
cat.sing();// 'Tom sing'
  • 基本类型的比较是值的比较,引用类型的比较是引用的比较
//基本数据类型
var a = 1;
var b = true;
var c = '1';
console.log(a == b);//true
console.log(a === b);//false
console.log(a == c);//true
console.log(a === c);//false

这个栗子,其实是为了说明基本数据类型的比较是值的比较,只有当两者的值相等的情况下它们才相等,但栗子中的布尔类型和数字类型使用==符号比较会相等,其实是类型转换==符号本身特性作用的结果,因此,这里也暴露出一个使用问题就是当我们在实际应用中尽量使用===对数据进行比较,从而消除因类型转换导致的不符合预期效果的错误结果。

//引用数据类型
var obj1 = {};
var obj2 = {};
console.log(obj1 == obj2); // false

为什么不相等呢?答案显而易见,就是因为引用类型的比较是引用地址的比较,obj1obj2是两个独立声明的变量,所以内存中所分配的空间是不同的区域,那么引用地址也是不相同的,相当于两个不同房间对应的房间号是不同的,对比引用地址,地址不同,所以就不相等。

  • 基本类型存储在栈内存,引用类型存储在栈内存和堆内存
    • 基本类型数据的存储位置是在 栈内存 中,会将声明变量的标识符,值进行存储,我的看法是因为基本类型的数据结构简单以及占用内存空间小的原因。
    • 引用类型数据的存储位置分两部分:栈内存 存储声明变量的标识符和指向堆内存中存储的值的指针堆内存 存储实际的值(对象),外部访问的时候是通过指针来间接访问到数据对象。

类型判断

  • typeof

typeof 运算符用来判断数据类型,它会返回一个表示数据类型的字符串,它可以准确判断的数据类型有numberstringbooleanundefinedfunction,但对于null和除过function以外的引用类型就力不从心,均会返回object

 typeof 1; // number
 typeof '';//string
 typeof true; //boolean
 typeof undefined; //undefined
 typeof new Function(); //function
 typeof null; //object 无效
 typeof [] ; //object 无效
 typeof new Date(); //object 无效
 typeof new RegExp(); //object 无效
  • instanceof
    instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链,这是官方的解释,感觉有点绕,简单的讲就是判断运算符左操作数对象的原型链上是否有右边这个构造函数的 prototype属性,也就是说指定对象是否是某个构造函数的实例,最后返回布尔值。
var num = 123
num instanceof Number //false
[] instanceof Array; //true
[] instanceof Object; //true
{} instanceof Array; //false
new Date() instanceof Date;//true
new Date() instanceof Object;//true
function Person(){};
new Person() instanceof Person;//true
new Person() instanceof Object;//true

首先说明一点,instanceof 运算符只能用于引用类型的判断,无法应用于基本数据类型判断。从上面这个栗子我们可以看出所有的引用类型数据都是 Object类型的实例(原型链的顶端就是Object对象),因此在实际应用中一般使用xxx instanceof Array或者xxx instanceof Function,而不会使用xxx instanceof Object

  • Object.prototype.toString

首先看一个栗子

    const dataArr = [1,'str',true,null,undefined,{},[],new Function(),new Date(),new RegExp()];
    
    for(let item of dataArr) {
        const type = typeOf(item);
        console.log(type); 
        //依次返回:number、 string、 boolean、 null、 
        //undefined、 object、 array、 function、 date、 regExp
    }
             
    function typeOf(obj) {
        const toString = Object.prototype.toString;
        const map = {
            "[object Boolean]": "boolean",
            "[object Number]": "number",
            "[object String]": "string",
            "[object Function]": "function",
            "[object Array]": "array",
            "[object Date]": "date",
            "[object RegExp]": "regExp",
            "[object Undefined]": "undefined",
            "[object Null]": "null",
            "[object Object]": "object"
        };
        return map[toString.call(obj)]; //Object.prototype.toString.call(obj) 或者 Object.prototype.toString.apply(obj)
    }

可以看到toString()几乎可以准确检测所有的数据,因此是一个具有普遍适用性的通用方法。并且使用的时候需要采用.call(.apply)的调用方式,这里主要原因是默认情况下(toString方法没有被复写),该方法会默认返回其调用者的具体类型,也就是toString运行时this指向的对象类型, 其返回的类型格式为[object,xxx]。所以使用.call(thisArg)或者.apply(thisArg)thisArg参数就是我们需要检测的目标,也就是this所指的调用对象,这样就会返回当前检测目标的准确数据类型。