在前端开发中,很多小伙伴在使用javascript的时候只重视其功能性而忽视了其语言性,这不利于我们养成良好的编程习惯。这里给大家详细总结一下javascript的数据类型。
熟悉java的同学知道,java里分为八大数据类型分别是boolean byte char short int long float double,而javascript的数据类型要多于java,可以按照内存使用的不同分为2大类。
两种数据类型及内存分布
第一类叫基本类型 有 null undefined boolean string number symbol 基本类型顾名思义就是由基本数据构成的,是直接存在内存的栈中的,基本类型的值是不可以改变的,当给一个基本类型重新赋值的时候,会在内存中开辟一个新的空间,旧值和新值是不同的指向。
基本类型中除了null都是值类型,值类型在栈内存中的形态如下图,其中包含了变量的名称和变量的值。let a = 1,flag = true,name = "panda"
null这个值比较特殊,按照大类划分属于基本类型,按照内存又属于引用类型,不过它的指针指向为空。
栈内存区域 | |
a | 1 |
flag | true |
name | panda |
第二类叫引用类型 有 object array function ,引用类型其实都可以称为对象。引用类型的值是可以改变的,并可以添加属性和方法,和基本类型不同,引用类型实际上是通过存储在栈内存中的值指向到了堆内存中的,构成两者的关系叫做指向或者指针,下面画一个图表示引用类型在堆内存中的形态。function属于特殊引用类型,但不属于数据储存,所以没有下面会提到的深浅拷贝问题。
let a = {};
let b = a;
新建一个对象a,在栈内存中的key是对象名a value是内存地址,同时,会在堆内存中开辟一个空间,key是内存地址,value是对象的值。
当我们执行b=a的时候,会在栈中开辟个新空间,而不会在堆中开辟一个新空间,因此a和b的内存地址都等于a的内存地址。
什么是深拷贝?什么是浅拷贝?
从图上我们可以看出来,引用类型的赋值,其实就是栈区的指针的赋值。设想一下,这个时候我们把a赋值给b,b和a指向的就是同一个堆地址,这个时候无论改变a还是b都会改变另一个。这里强调一点,引用类型的比较其实是引用的比较,而不是大家理解的值的比较,用代码表示就是
let obj1 = {}; let obj2 = {}; obj1==obj2 // false
回到刚才对象赋值会互相影响的问题,在我们日常开发中,会经常遇到需要copy对象的情况,比如data1为模板数据,想拷贝data1赋值给data2,我们改变data2的时候不想改变模板的数据,这里就引出了深拷贝和浅拷贝的概念。这里申明一点,深拷贝和浅拷贝都是为了解决对象赋值的问题的,有的同学理解为let data2 = data1 这样就是浅拷贝,连同引用地址一起改变了这样就是深拷贝,这种理解是错误的。
从概念上来说 只改变数据第一层的指向的拷贝叫做浅拷贝,改变数据所有内容指向的拷贝叫做深拷贝。一半我们写代码的时候浅拷贝基本可以满足需求,但是遇到复杂的数据的时候是需要深拷贝实现的
浅拷贝的方法:
第一种:
深拷贝的方法:
第二种:给大家提供一个深拷贝函数,原理是通过递归
function deepClone(target) { // 定义一个变量 let result; // 如果当前需要深拷贝的是一个对象的话 if (typeof target === 'object'||typeof target== null) { // 如果是一个数组的话 if (Array.isArray(target)) { result = []; // 将result赋值为一个数组,并且执行遍历 for (let i in target) { // 递归克隆数组中的每一项 result.push(deepClone(target[i])) } // 判断如果当前的值是null的话;直接赋值为null } else if(target===null) { result = null; // 判断如果当前的值是一个RegExp对象的话,直接赋值 } else if(target.constructor===RegExp){ result = target; }else { // 否则是普通对象,直接for in循环,递归赋值对象的所有值 result = {}; for (let i in target) { result[i] = deepClone(target[i]); } } // 如果不是对象的话,就是基本数据类型,那么直接赋值 } else { result = target; } // 返回最终结果 return result; } let c = { name:undefined, fun:function(){}, val:1 } c.age = c.name c.val2 = c.val let d = deepClone(c) console.log("递归复制:",d)
如何判断数据类型
了解了两种数据类型的不同之后,我们还要了解如何去判断一个数据类型,这里给大家列举了三个方法:
第一种是typeof 返回字符串 用法如下:
需要注意的是 typeof 返回6种结果,对象和数组返回object ,函数返回function ,基本类型中null返回object(算是js的一个bug),NaN代表空数字也返回nubmer,
用途:1 可以识别所有值类型(除了null之外的5种基本类型) 2 识别函数 3 判断是否是引用类型(判断不出来具体是哪一种引用类型)
var a = [34,4,3,54], b = 34, c = 'adsfas', d = function(){console.log('我是函数')}, e = true, f = null, g; console.log(typeof(a));//object console.log(typeof(b));//number console.log(typeof(c));//string console.log(typeof(d));//function console.log(typeof(e));//boolean console.log(typeof(f));//object console.log(typeof(g));//undefined
第二种是 instanceof 返回布尔值:
instanceof一般用来判断 a是不是b的实例,利用这一点可以精确判断出对象的数据类型,涉及原型和原型链的知识后面会单独写一篇文章为大家讲解
用法:
[] instanceof Array //true {} instanceof Object//true function(){} instanceof Function//true
100 instanceof Array //false
"panda" instanceof object //false
第三种是 Object.prototype.toString.call() 方法,用这个方法可以精确判断出所有数据类型(推荐)
Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(“abc”);// "[object String]" Object.prototype.toString.call(123);// "[object Number]" Object.prototype.toString.call(true);// "[object Boolean]"
**函数类型**
Function fn(){
console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"
**日期类型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"
**数组类型**
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"
**正则表达式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"