JavaScript数据类型详解
JavaScript 数据类型
基本类型:字符串(String
)、数字(Number
)、布尔(Boolean
)、对空(Null
)、未定义(Undefined
)、Symbol
、以及ES10
新增的BigInt
(任意精度整数)七类。
引用数据类型:对象(Object
)、数组(Array
)、函数(Function
)。
注:
Symbol
是ES6
引入了一种新的原始数据类型,表示独一无二的值。
数据存放
JavaScript
的所有变量(包括函数)在整个处理过程中都是存放在内存中,所以要对一个变量进行处理。首先得为变量分配内存。
JavaScript
内存分配和其他语言一样,是根据变量的数据类型来分配内存的,而JavaScript
变量的数据类型由所赋的值的类型所决定的。在JavaScript
中,基本数据类型变量分配在栈内存中,其中存放了变量的值,对其是按值访问的;而引用数据类型的变量则同时会分配栈内存和堆内存,其中栈内存存放的是地址。堆内存存放的是引用的值,栈内存存放的地址指向堆内存存放的值。对该变量的访问是按引用来访问的,即首先读取到栈内存存放的地址,然后按地址找到堆内存读取其中的值。如下图:
基本数据类型
引用数据类型
JavaScript
之所以按变量的不同数据类型来分配内存,主要原因是栈内存比堆内存小,而且栈内存的大小是固定的,而堆内存大小可以动态变化。基本数据类型的值的大小固定,对象类型的值大小不固定,所以将它们分别存放在栈内存和堆内存是合理的。
判断变量的数据类型
typeof
typeof
是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括number
,string
,boolean
,undefined
,object
,function
,symbol
等。
1 | typeof ""; //string |
instanceof
instanceof
用来判断A
是否为B
的实例,表达式为:A instanceof B
,如果A
是B
的实例,则返回true
,否则返回false
。instanceof
检测的是原型,内部机制是通过判断对象的原型链中是否有类型的原型。
1 | {} instanceof Object; //true |
constructor
当一个函数F
被定义时,JS
引擎会为F
添加prototype
原型,然后在prototype
上添加一个constructor
属性,并让其指向F的引用,F
利用原型对象的constructor
属性引用了自身,当F作为构造函数创建对象时,原型上的constructor
属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F
就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型。
Object.prototype.toString()
toString()
是Object
的原型方法,调用该方法,默认返回当前对象的[[Class]]
。这是一个内部属性,其格式为[object Xxx]
,其中Xxx
就是对象的类型。 对于Object
对象,直接调用toString()
就能返回[object Object]
,而对于其他对象,则需要通过call
、apply
来调用才能返回正确的类型信息。
装一个准确判断数据类型的函数
1 | /** |
引用数据类型的深拷贝
上面说了,引用数据类型的变量会分配栈内存和堆内存,其中栈内存存放的是地址,当我们把引用数据类型变量a
赋值给变量b
,例如:
let a = [0, 1, 2, 3, 4], b = a
,当变量b
发生改变后,变量a
也相应发生了改变。
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们b[0]=1
时进行数组修改时,由于a
与b
指向的是同一个地址,所以自然a
也受了影响,这就是所谓的浅拷贝了。
浅拷贝:只是拷贝的栈区的引用地址
根据浅拷贝的方法不同,会有不同的效果。
一、最弱的浅拷贝为直接赋值
1 | let b = a; |
是直接将整个a
数组的地址赋给b
,故b
的任意值(为什么说任意值呢?因为后面会介绍到,有些浅拷贝,可以使部分值看上去有深拷贝的效果)改变,都会影响到a
二、下面四种浅拷贝方法就厉害一些了,它们在有些情况(原数组里的数据不包含引用类型)下也能达到深拷贝效果(没错只是披着狼皮的小绵羊啦,本质还是浅拷贝)
一个数组变化,另一个数组不受影响。
原数组里的数据不包含引用类型
1 | let a = [0,1,2,3,4] // 原数组 |
1.使用拓展运算符
1 | let b = [...a]; |
2.使用assign()
1 | let b = Object.assign([], a) |
3.使用concat()
1 | let b = [].concat(a); |
因为 concat()
返回的是一个副本,所以这个时候改变 b
就不会导致 a
改变了。
4.使用slice()
1 | let arr2 = arr1.slice(0); |
深拷贝:就是不管里面多少层,都遍历,克隆一个与旧不相关的,修改新的不影响旧的。
原数组里的数据包含引用类型
1 | let a = [1 , 2 , 3 , {"key" : "value"} , {"key1" : "value1"}]; //原数组 |
使用上述方法均不能实现全部深拷贝,非引用类型的值不会受影响,嵌套的一层引用类型的值会受影响。
解决方法:
方法一:递归
1 | function deepClone(obj) { |
方法二:通过JSON
解析解决
1 | let b = JSON.parse(JSON.stringify(a)); |
注意:这种方法拷贝后的数组会丢失原数组中定义的方法和数组原型中定义的方法。