抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Mr.wang

Time flies and people come and go

JavaScript数据类型的区别,转换,隐藏bug

一、JavaScript数据类型

ECMAScript标准规定了7种数据类型,其把这7种数据类型又分为两种:原始类型和对象类型。

原始类型

  • Null:只包含一个值:null
  • Undefined:只包含一个值:undefined
  • Boolean:包含两个值:truefalse
  • Number:整数或浮点数,还有一些特殊值(-Infinity+InfinityNaN
  • String:一串表示文本值的字符序列
  • Symbol:一种实例是唯一且不可改变的数据类型

(在es10中加入了第七种原始类型BigInt,现已被最新Chrome支持)

对象类型

  • Object:自己分一类丝毫不过分,除了常用的ObjectArrayFunction等都属于特殊的对象

    区分原始类型和对象类型 - 不可变性

原始类型存放在栈空间中,而复杂类型存放在堆空间中

栈内存:

  • 存储的值大小固定
  • 空间较小
  • 可以直接操作其保存的变量,运行效率高
  • 由系统自动分配存储空间

堆内存:

  • 存储的值大小不定,可动态调整
  • 空间较大,运行效率低
  • 无法直接操作其内部存储,使用引用地址读取
  • 通过代码进行分配空间

字符串 具备不可变性

不管是字符串的裁切splice 还是 toLowerCase 都是在原有的基础上产生新的字符串

JavaScript中的原始类型的值被直接存储在栈中,在变量定义时,栈就为其分配好了内存空间。

由于栈中的内存空间的大小是固定的,那么注定了存储在栈中的变量就是不可变的

对象 具备可变性

引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值。

数组 是一个复杂(引用)类型

通过一些api可以改变数组

  • pop() 删除数组最后一个元素,如果数组为空,则不改变数组,返回undefined,改变原数组,返回被删除的元素
  • push()向数组末尾添加一个或多个元素,改变原数组,返回新数组的长度
  • shift()把数组的第一个元素删除,若空数组,不进行任何操作,返回undefined,改变原数组,返回第一个元素的值
  • unshift()向数组的开头添加一个或多个元素,改变原数组,返回新数组的长度
  • reverse()颠倒数组中元素的顺序,改变原数组,返回该数组
  • sort()对数组元素进行排序,改变原数组,返回该数组
  • splice()从数组中添加/删除项目,改变原数组,返回被删除的元素

当我们进行复制时两者有着本质的区别

这里涉及到浅拷贝和深拷贝—本质是复制时是否改变复制的对象的数值或属性或方法

原始类型

1
2
3
4
5
var name = 'wang';
var newName = name;
newName = 'Mr.wang';
console.log(name) // =>wang
console.log(newName) // =>Mr.wang

内存中有一个变量name,值为wang。我们从变量name复制出一个变量newName,此时在内存中创建了一个块新的空间用于存储wang,虽然两者值是相同的,但是两者指向的内存空间完全不同,这两个变量参与任何操作都互不影响。

引用类型

1
2
3
4
5
var obj = {name:'wang'};
var newObj = obj;
newObj.name = 'Mr.wang';
console.log(obj.name) //=> Mr.wang
console.log(newObj.name)//=> Mr.wang

当我们复制引用类型的变量时,实际上复制的是栈中存储的地址,所以复制出来的newObj实际上和obj指向的堆中同一个对象。因此,我们改变其中任何一个变量的值,另一个变量都会受到影响,这就是为什么会有深拷贝和浅拷贝的原因。

原始类型和引用类型的比较

原始类型,比较时会直接比较它们的值,如果值相等,即返回true

引用类型,比较时会比较它们的引用地址,虽然两个变量在堆中存储的对象具有的属性值都是相等的,但是它们被存储在了不同的存储空间,因此比较值为false

区分null和undefined

null

表示被赋值过的对象,刻意把一个对象赋值为null,故意表示其为空,不应有值。

所以对象的某个属性值为null是正常的,null转换为数值时值为0

undefined

表示“缺少值”,即此处应有一个值,但还没有定义,

如果一个对象的某个属性值为undefined,这是不正常的,如obj.name=undefined,我们不应该这样写,应该直接delete obj.name

undefined转为数值时为NaN(非数字值的特殊值)nullAndundefined.png

JavaScript是一门动态类型语言,成员除了表示存在的空值外,还有可能根本就不存在(因为存不存在只在运行期才知道),这就是undefined的意义所在。对于JAVA这种强类型语言,如果有"undefined"这种情况,就会直接编译失败,所以在它不需要一个这样的类型。

你想不到的0.1+0.2≠0.3

本质是:由于数字在js中的计算是先转换为二进制在进行计算的,然后以十进制输出(大部分语言都有这个问题,是cpu计算二进制的问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function judgeFloat(n, m) {
const binaryN = n.toString(2);
const binaryM = m.toString(2);
console.log(`${n}的二进制是 ${binaryN}`);
console.log(`${m}的二进制是 ${binaryM}`);
const MN = m + n;
const accuracyMN = (m * 100 + n * 100) / 100;
const binaryMN = MN.toString(2);
const accuracyBinaryMN = accuracyMN.toString(2);
console.log(`${n}+${m}的二进制是${binaryMN}`);
console.log(`${accuracyMN}的二进制是 ${accuracyBinaryMN}`);
console.log(`${n}+${m}的二进制再转成十进制是${to10(binaryMN)}`);
console.log(`${accuracyMN}的二进制是再转成十进制是${to10(accuracyBinaryMN)}`);
console.log(`${n}+${m}在js中计算是${(to10(binaryMN) === to10(accuracyBinaryMN)) ? '' : '不'}准确的`);
}
function to10(n) {
const pre = (n.split('.')[0] - 0).toString(2);
const arr = n.split('.')[1].split('');
let i = 0;
let result = 0;
while (i < arr.length) {
result += arr[i] * Math.pow(2, -(i + 1));
i++;
}
return result;
}
judgeFloat(0.1, 0.2);
judgeFloat(0.6, 0.7);

0.1+0.2

计算机中所有的数据都是以二进制存储的,所以在计算时计算机要把数据先转换成二进制进行计算,然后在把计算结果转换成十进制

由上面的代码不难看出,在计算0.1+0.2时,二进制计算发生了精度丢失,导致再转换成十进制后和预计的结果不符。

隐式转换

隐式转换

if语句和逻辑语句

if语句和逻辑语句中,如果只有单个变量,会先将变量转换为Boolean值,只有下面几种情况会转换成false,其余被转换成true

1
2
3
4
5
6
7
null
undefined
''
NaN
0
false

各种运数学算符

我们在对各种非Number类型运用数学运算符(- * /)时,会先将非Number类型转换为Number类型;

1
2
3
4
5
1 - true // 0
1 - null // 1
1 * undefined // NaN
2 * ['5'] // 10

注意+是个例外,执行+操作符时:

  • 1.当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
  • 2.当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
  • 3.当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。
1
2
3
4
5
123 + '123' // 123123   (规则1)
123 + null // 123 (规则2)
123 + true // 124 (规则2)
123 + {} // 123[object Object] (规则3)

== 运算符

使用==时,若两侧类型相同,则比较结果和===相同,否则会发生隐式转换,使用==时发生的转换可以分为几种不同的情况(只考虑两侧类型不同):

  • 1.NaN

NaN和其他任何类型比较永远返回false(包括和他自己)。

1
2
NaN == NaN // false

  • 2.Boolean

Boolean和其他任何类型比较,Boolean首先被转换为Number类型。

1
2
3
4
5
true == 1  // true 
true == '2' // false
true == ['1'] // true
true == ['2'] // false

这里注意一个可能会弄混的点:undefined、nullBoolean比较,虽然undefined、nullfalse都很容易被想象成假值,但是他们比较结果是false,原因是false首先被转换成0

1
2
3
undefined == false // false
null == false // false

  • 3.String和Number

StringNumber比较,先将String转换为Number类型。

1
2
3
123 == '123' // true
'' == 0 // true

  • 4.null和undefined

null == undefined比较结果是true,除此之外,null、undefined和其他任何结果的比较值都为false

1
2
3
4
5
6
7
null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false
  • 5.原始类型和引用类型

当原始类型和引用类型做比较时,对象类型会依照ToPrimitive规则转换为原始类型:

1
2
3
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true

来看看下面这个比较:

1
2
[] == ![] // true

!的优先级高于==![]首先会被转换为false,然后根据上面第三点,false转换成Number类型0,左侧[]转换为0,两侧比较相等。

1
2
3
[null] == false // true
[undefined] == false // true

根据数组的ToPrimitive规则,数组元素为nullundefined时,该元素被当做空字符串处理,所以[null]、[undefined]都会被转换为0

原型链的规则

  • 1.所有引用类型都具有对象特性,即可以自由扩展属性
  • 2.所有引用类型都具有一个proto(隐式原型)属性,是一个普通对象
  • 3.所有的函数都具有prototype(显式原型)属性,也是一个普通对象
  • 4.所有引用类型proto值指向它构造函数的prototype
  • 5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的proto中去找

toString

上面我们在拆箱操作中提到了toString函数,我们可以调用它实现从引用类型的转换。

每一个引用类型都有toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

tostring

0.11加上0.1不等于0.1

解决方法

1
parseFloat((prev + curr).toFixed(2))

JavaScript怎么把两个数组里的多个对象一一对应合并?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var obj1 = [{
"id": 980550455852,
"model": "XQG70-S1208FW",
"color": "白",
"invStatusName": "正品",
"bactualQty": 10947,
"brealyQty": 11000,
"bavailQty": 53
}, {
"id": 980550566221,
"model": "XQB70-C3006",
"color": "灰",
"invStatusName": "正品",
"bactualQty": 11,
"brealyQty": 6,
"bavailQty": -5
}];

var obj2 = [{
"price": "6666"
}, {
"price": "8888"
}];
var obj = obj1.map((item,index) => {
return {...item, ...obj2[index]};
});
console.log(obj); //

评论