JavaScript中的数据类型
原始类型和对象类型
JavaScript的数据类型分为原始类型和对象类型。
原始类型: Number、String、Boolean、Undefined、Null、Symbol、BigInt
对象类型: Object
typeof
在开发中,一般会使用typeof去判断数据类型。
typeof 1 // "number"
typeof 'a' // "string"
typeof false // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 18n // "bigint"
typeof null // "object"
typeof {} // "object"
从上面结果中可以看出,null 和 {}得到的数据类型字符串都是object,这是为什么?
这其实是JavaScript设计之初遗留下来的问题。在JavaScript设计的第一个版本中,所有的值都存储在32位单元中,而每个单元又包含类型标签和真正要存储的数据。一般前几位用来存储类型标签,表示数据类型信息,对象类型的类型标签为
000,而null的值为全0,这就导致typeof 判断null和对象类型得到的结果一致。
所以说用typeof判断数据类型并不严谨,此外想要知道区分数组和普通对象,用typeof就不能做到了,因为typeof 数组和typeof 对象得到的结果都是object。有什么方法可以明确知道一个变量就是数组呢?
instanceof
instanceof 操作符的原理是判断构造函数的prototype属性是否在一个对象实例的原型链上。
[] instanceof Array // true
new Date() instanceof Date // true
手写instanceof
function myInstanceof(obj, constrcutor) {
const prototype = constrcutor.prototype // 构造函数的prototype
while (obj !== null) {
let proto = Object.getPrototypeOf(obj) // obj的原型
if (obj === prototype) return true // 找到,退出循环
obj = proto // 继续沿着obj的原型链查找
}
return false // 找到原型链顶端也没有找到构造函数的prototype属性,返回false
}
数据类型转换
在开发中,经常遇到的就是String、Number、Boolean和Object这几种类型的转换。在JavaScript中,一般分为强制类型转换和隐式类型转换,隐式转换基于强制转换。
强制转换
在javascript中,强制转换一般主要通过String()、Number()和Boolean()三个函数实现。
Number()
- 字符串转数字
console.log(Number('123')) // 123
console.log(Number(' 123')) //123
console.log(Number(' 12 3')) // NaN
console.log(Number(' 123 ')) //123
console.log(Number('123a')) //NaN
console.log(Number('a')) // NaN
console.log(Number('')) // 0
console.log(Number('\t\n\r')) //0
字符串转换为数字规则如下:
- 数字字符串转换为对应的数字
- 数字字符串可以允许前后有空格,但中间有空格,会转换为NaN
- 含有非数字的单词字符会转换为NaN
- 空字符串转换为0,即使是有\t、\n和\r等特殊字符都转换为0
- 布尔类型转换为数字
// true -> 1 false -> 0
console.log(Number(true)) // 1
console.log(Number(false)) // 0
- null 转换为数字
console.log(Number(null)) // 0
- undefined 转换为数字
console.log(Number(undefined)) // NaN
注意: null转换为数字为0,而undefined转为数字是NaN。
- 对象类型转换为数字
console.log(Number({a: 1})) // NaN
console.log(Number([1,2,4])) // NaN
console.log(Number([1])) // 1
为什么有些对象转换为数字为NaN,而有些能够得到正确的数字呢?
其实,对象类型转换为数字类型,会经历两个步骤:
首先会转换为原始类型
得到的原始类型再用Number函数转换为数字类型
对象转数字类型又分为以下三个步骤:
- 对象如果有[Symbol.toPrimitive]属性: 这个属性是一个函数,此函数返回的结果是原始类型,将结果作为转换后的值,如果返回的结果还是一个对象,那么就会报错
TypeError: Cannot convert object to primitive value- 没有[Symbol.toPrimitive]属性,会调用对象valueOf方法,如果此方法返回的结果依旧不是原始类型,会进行第三步
- 调用对象的toString方法,如果返回的是原始类型,即是转换后的最终值,如果此方法返回的结果依旧是对象类型,那么就会报错
TypeError: Cannot convert object to primitive value
const obj1 = {
[Symbol.toPrimitive] () {
return '123'
}
}
const obj2 = {
[Symbol.toPrimitive] () {
return {}
}
}
const obj3 = {
valueOf () {
return '123'
}
}
const obj4 = {
valueOf () {
return {}
},
toString() {
return '123'
}
}
const obj5 = {
valueOf () {
return {}
},
toString() {
return {}
}
}
console.log(Number(obj1)) // 123
console.log(Number(obj2)) // 报错
console.log(Number(obj3)) // 123
console.log(Number(obj4)) // 123
console.log(Number(obj5)) // 报错
再来看看下面的代码:
console.log(Number({a: 1})) // NaN
console.log(Number([1,2,4])) // NaN
console.log(Number([1])) // 1
一般来说,我们平常书写的普通对象、数组一般都是没有[Symbol.toPrimitive]属性,valueOf方法通常返回对象本身,所以说就会调用对象toString方法。
console.log(Number({a: 1})) // {a: 1} --> "[object Object]" --> NaN
console.log(Number([1,2,4])) // [1,2,4] --> "1,2,4" --> NaN
console.log(Number([1])) // [1] --> "1" --> 1
String()
- 原始类型转换为字符串
console.log(String(123)) // '123'
console.log(String('abc')) // 'abc'
console.log(String(true)) // 'true'
console.log(String(false)) // 'false'
console.log(String(null)) // 'null'
console.log(String(undefined)) // 'undefined'
原始类型转换为字符串比较简单,一般都是转换为对应的字符串值。
- 对象类型转换为字符串
对象转换为字符串和数字转换为字符串的步骤大致相似:
对象转换为原始类型,再调用String函数
只不过在转为原始类型时有一点不同,是在第二步骤时先调用toString方法,再调用valueOf方法。
- 先看对象是否有
[Symbol.toPrimitive]属性,调用此函数,返回的结果是原始类型,则使用该值,如果依旧是对象类型,则报错- 调用对象的
toString方法,返回的的结果是原始类型,则不需要走第三步,如果是对象类型,则走第三步- 调用对象的
valueOf方法,返回的结果是原始类型,则用此值调用String();如果还是对象则报错
const obj1 = {
[Symbol.toPrimitive] () {
return 'abc'
}
}
const obj2 = {
[Symbol.toPrimitive] () {
return {}
}
}
const obj3 = {
valueOf () {
return 'abc'
},
toString() {
return '123'
}
}
const obj4 = {
valueOf () {
return 'abc'
},
toString () {
return {}
}
}
const obj5 = {
valueOf () {
return {}
},
toString () {
return {}
}
}
console.log(String(obj1)) // 'abc'
console.log(String(obj2)) // 报错
console.log(String(obj3)) // '123'
console.log(String(obj4)) // 'abc'
console.log(String(obj5)) // 报错
Boolean()
将其他类型转换为布尔类型,相对简单,除了以下几个值转换为false, 其他值都转换为true
NaN
0
null
undefined
''
console.log(Boolean(NaN)) // false
console.log(Boolean(0)) // false
console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean('')) // false
再加上false本身,在JavaScript中,一共就6个值用Boolean()转换为false。
任何对象类型的值都转换为true,即使是new Boolean(false)这样的包装类型,也是转换为true
const obj1 = {
[Symbol.toPrimitive] () {
return false
}
}
console.log(Boolean(obj1)) // true
console.log(Boolean(new Boolean(false))) // true
隐式转换
在JavaScript中通常是在以下三种情况下,会进行自动类型转换
- 不同类型的数据进行算术运算
- 需要布尔判定时,如果if判断、while的条件判断、三目运算符
是否为真 ? true : false、取反运算!数据 - 对非数值进行一元运算符
+、-运算
console.log('1' + '2') // 12
console.log('1' - '2') // -1
console.log(1 + '2') // 12
console.log(null + 1) // 1
console.log(undefined + 1) // NaN
console.log(null + '1') // null1
console.log(false + 1) // 1
console.log({} + 1) // [object Object]1
console.log(![]) // false
console.log(+null) // 0
console.log(-null) // -0
console.log(-{}) // NaN
乍一看上面得到的结果有点乱,毫无规律可言,其实不然。自动类型转换无外乎自动转换为数字、字符串和布尔类型这三种情况。
- 自动转换为数字
当进行算术运算和一元运算符
+、-,通常需要将其他非数值类型的数据转换为数字。除了+运算符一侧存在字符串的情况。
console.log('5' - '1')
console.log(null + 1)
console.log(undefined + 1)
console.log(false - 1)
console.log(true * 2)
console.log(+false) // 0
console.log(-'5') // -5
console.log(+undefined) // NaN
以上是原始类型转换为数字,当存在对象时,那么就要先将对象转原始, 再参与运算
const obj1 = {
toString () {
return 10
}
}
console.log(obj1 + 1) // 11
- 自动转换为字符串
自动转字符串通常出现在字符串拼接的时候,就是
+运算符一端存在字符串。
console.log('1' + 1) // '11'
console.log('1' + null) // '1null'
console.log('1' + undefined) // '1undefined'
console.log('1' + false) // '1false'
console.log('1' + true) // '1true'
console.log(1 + {}) // '1[object Object]'
console.log(1 + []) // '1'
当参与的类型存在对象时,一般转换为原始类型,转换后的原始类型是字符串,那么就会进行字符串拼接。这也就是1+{}的结果会是1[object Object]的原因。
- 自动转换为布尔类型 自动转换为布尔类型主要就出现以下几种情况: if判断、while条件判断、取反、三目运算符的条件判断。
除了以下情况:
0
NaN
null
undefined
''
false
都转换为false,都会自动转换为true。这与Boolean强制类型转换一致。
if (!0 && !NaN && !null && !undefined && !false) {
console.log(true) // true
}
if判断里的值取反之后都为true,故最后输出true