2000字+1脑图彻底弄懂JS数据类型中的转换规则

149 阅读7分钟

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()

  1. 字符串转数字
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
  1. 布尔类型转换为数字
// true -> 1  false -> 0
console.log(Number(true))  // 1
console.log(Number(false))  // 0
  1. null 转换为数字
console.log(Number(null))  // 0
  1. undefined 转换为数字
console.log(Number(undefined))   // NaN

注意: null转换为数字为0,而undefined转为数字是NaN。

  1. 对象类型转换为数字
console.log(Number({a: 1}))   // NaN
console.log(Number([1,2,4]))   // NaN
console.log(Number([1]))  // 1

为什么有些对象转换为数字为NaN,而有些能够得到正确的数字呢?

其实,对象类型转换为数字类型,会经历两个步骤:

  1. 首先会转换为原始类型

  2. 得到的原始类型再用Number函数转换为数字类型

对象转数字类型又分为以下三个步骤:

  1. 对象如果有[Symbol.toPrimitive]属性: 这个属性是一个函数,此函数返回的结果是原始类型,将结果作为转换后的值,如果返回的结果还是一个对象,那么就会报错TypeError: Cannot convert object to primitive value
  2. 没有[Symbol.toPrimitive]属性,会调用对象valueOf方法,如果此方法返回的结果依旧不是原始类型,会进行第三步
  3. 调用对象的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()

  1. 原始类型转换为字符串
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'

原始类型转换为字符串比较简单,一般都是转换为对应的字符串值。

  1. 对象类型转换为字符串

对象转换为字符串和数字转换为字符串的步骤大致相似:

对象转换为原始类型,再调用String函数

只不过在转为原始类型时有一点不同,是在第二步骤时先调用toString方法,再调用valueOf方法。

  1. 先看对象是否有[Symbol.toPrimitive]属性,调用此函数,返回的结果是原始类型,则使用该值,如果依旧是对象类型,则报错
  2. 调用对象的toString方法,返回的的结果是原始类型,则不需要走第三步,如果是对象类型,则走第三步
  3. 调用对象的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中通常是在以下三种情况下,会进行自动类型转换

  1. 不同类型的数据进行算术运算
  2. 需要布尔判定时,如果if判断、while的条件判断、三目运算符是否为真 ? true : false、取反运算!数据
  3. 对非数值进行一元运算符+-运算
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

乍一看上面得到的结果有点乱,毫无规律可言,其实不然。自动类型转换无外乎自动转换为数字、字符串和布尔类型这三种情况。

  1. 自动转换为数字 当进行算术运算和一元运算符+、-,通常需要将其他非数值类型的数据转换为数字。除了+运算符一侧存在字符串的情况。
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
  1. 自动转换为字符串 自动转字符串通常出现在字符串拼接的时候,就是+运算符一端存在字符串。
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]的原因。

  1. 自动转换为布尔类型 自动转换为布尔类型主要就出现以下几种情况: if判断、while条件判断、取反、三目运算符的条件判断。

除了以下情况:

0
NaN
null
undefined
''
false

都转换为false,都会自动转换为true。这与Boolean强制类型转换一致。

if (!0 && !NaN && !null && !undefined && !false) {
    console.log(true)  // true
}

if判断里的值取反之后都为true,故最后输出true

总结

数据类型.png