JavaScript数据类型的区别,转换,隐藏bug
一、JavaScript数据类型
ECMAScript标准规定了7种数据类型,其把这7种数据类型又分为两种:原始类型和对象类型。
原始类型
Null:只包含一个值:nullUndefined:只包含一个值:undefinedBoolean:包含两个值:true和falseNumber:整数或浮点数,还有一些特殊值(-Infinity、+Infinity、NaN)String:一串表示文本值的字符序列Symbol:一种实例是唯一且不可改变的数据类型
(在es10中加入了第七种原始类型BigInt,现已被最新Chrome支持)
对象类型
Object:自己分一类丝毫不过分,除了常用的Object,Array、Function等都属于特殊的对象区分原始类型和对象类型 - 不可变性
原始类型存放在栈空间中,而复杂类型存放在堆空间中
栈内存:
- 存储的值大小固定
- 空间较小
- 可以直接操作其保存的变量,运行效率高
- 由系统自动分配存储空间
堆内存:
- 存储的值大小不定,可动态调整
- 空间较大,运行效率低
- 无法直接操作其内部存储,使用引用地址读取
- 通过代码进行分配空间
字符串 具备不可变性
不管是字符串的裁切splice 还是 toLowerCase 都是在原有的基础上产生新的字符串
JavaScript中的原始类型的值被直接存储在栈中,在变量定义时,栈就为其分配好了内存空间。
由于栈中的内存空间的大小是固定的,那么注定了存储在栈中的变量就是不可变的
对象 具备可变性
引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值。
数组 是一个复杂(引用)类型
通过一些api可以改变数组
pop()删除数组最后一个元素,如果数组为空,则不改变数组,返回undefined,改变原数组,返回被删除的元素push()向数组末尾添加一个或多个元素,改变原数组,返回新数组的长度shift()把数组的第一个元素删除,若空数组,不进行任何操作,返回undefined,改变原数组,返回第一个元素的值unshift()向数组的开头添加一个或多个元素,改变原数组,返回新数组的长度reverse()颠倒数组中元素的顺序,改变原数组,返回该数组sort()对数组元素进行排序,改变原数组,返回该数组splice()从数组中添加/删除项目,改变原数组,返回被删除的元素
当我们进行复制时两者有着本质的区别
这里涉及到浅拷贝和深拷贝—本质是复制时是否改变复制的对象的数值或属性或方法
原始类型
1 | var name = 'wang'; |
内存中有一个变量name,值为wang。我们从变量name复制出一个变量newName,此时在内存中创建了一个块新的空间用于存储wang,虽然两者值是相同的,但是两者指向的内存空间完全不同,这两个变量参与任何操作都互不影响。
引用类型
1 | var obj = {name:'wang'}; |
当我们复制引用类型的变量时,实际上复制的是栈中存储的地址,所以复制出来的newObj实际上和obj指向的堆中同一个对象。因此,我们改变其中任何一个变量的值,另一个变量都会受到影响,这就是为什么会有深拷贝和浅拷贝的原因。
原始类型和引用类型的比较
原始类型,比较时会直接比较它们的值,如果值相等,即返回true。
引用类型,比较时会比较它们的引用地址,虽然两个变量在堆中存储的对象具有的属性值都是相等的,但是它们被存储在了不同的存储空间,因此比较值为false。
区分null和undefined
null
表示被赋值过的对象,刻意把一个对象赋值为null,故意表示其为空,不应有值。
所以对象的某个属性值为null是正常的,null转换为数值时值为0。
undefined
表示“缺少值”,即此处应有一个值,但还没有定义,
如果一个对象的某个属性值为undefined,这是不正常的,如obj.name=undefined,我们不应该这样写,应该直接delete obj.name。
undefined转为数值时为NaN(非数字值的特殊值)
JavaScript是一门动态类型语言,成员除了表示存在的空值外,还有可能根本就不存在(因为存不存在只在运行期才知道),这就是undefined的意义所在。对于JAVA这种强类型语言,如果有"undefined"这种情况,就会直接编译失败,所以在它不需要一个这样的类型。
你想不到的0.1+0.2≠0.3
本质是:由于数字在js中的计算是先转换为二进制在进行计算的,然后以十进制输出(大部分语言都有这个问题,是cpu计算二进制的问题)
1 | function judgeFloat(n, m) { |

计算机中所有的数据都是以二进制存储的,所以在计算时计算机要把数据先转换成二进制进行计算,然后在把计算结果转换成十进制。
由上面的代码不难看出,在计算0.1+0.2时,二进制计算发生了精度丢失,导致再转换成十进制后和预计的结果不符。
隐式转换

if语句和逻辑语句
在if语句和逻辑语句中,如果只有单个变量,会先将变量转换为Boolean值,只有下面几种情况会转换成false,其余被转换成true:
1 | null |
各种运数学算符
我们在对各种非Number类型运用数学运算符(- * /)时,会先将非Number类型转换为Number类型;
1 | 1 - true // 0 |
注意+是个例外,执行+操作符时:
- 1.当一侧为
String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。 - 2.当一侧为
Number类型,另一侧为原始类型,则将原始类型转换为Number类型。 - 3.当一侧为
Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。
1 | 123 + '123' // 123123 (规则1) |
== 运算符
使用==时,若两侧类型相同,则比较结果和===相同,否则会发生隐式转换,使用==时发生的转换可以分为几种不同的情况(只考虑两侧类型不同):
NaN和其他任何类型比较永远返回false(包括和他自己)。
1 | NaN == NaN // false |
Boolean和其他任何类型比较,Boolean首先被转换为Number类型。
1 | true == 1 // true |
这里注意一个可能会弄混的点:
undefined、null和Boolean比较,虽然undefined、null和false都很容易被想象成假值,但是他们比较结果是false,原因是false首先被转换成0:
1 | undefined == false // false |
String和Number比较,先将String转换为Number类型。
1 | 123 == '123' // true |
null == undefined比较结果是true,除此之外,null、undefined和其他任何结果的比较值都为false。
1 | null == undefined // true |
当原始类型和引用类型做比较时,对象类型会依照ToPrimitive规则转换为原始类型:
1 | '[object Object]' == {} // true |
来看看下面这个比较:
1 | [] == ![] // true |
!的优先级高于==,![]首先会被转换为false,然后根据上面第三点,false转换成Number类型0,左侧[]转换为0,两侧比较相等。
1 | [null] == false // true |
根据数组的ToPrimitive规则,数组元素为null或undefined时,该元素被当做空字符串处理,所以[null]、[undefined]都会被转换为0。
原型链的规则
- 1.所有引用类型都具有对象特性,即可以自由扩展属性
- 2.所有引用类型都具有一个
proto(隐式原型)属性,是一个普通对象 - 3.所有的函数都具有
prototype(显式原型)属性,也是一个普通对象 - 4.所有引用类型
proto值指向它构造函数的prototype - 5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的
proto中去找
toString
上面我们在拆箱操作中提到了toString函数,我们可以调用它实现从引用类型的转换。
每一个引用类型都有
toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString()返回"[object type]",其中type是对象的类型。

0.11加上0.1不等于0.1
解决方法
1 | parseFloat((prev + curr).toFixed(2)) |
JavaScript怎么把两个数组里的多个对象一一对应合并?
1 | var obj1 = [{ |