JavaScript 从基础到弃坑 - 数据类型转换

ℹ️ 本文发布于请注意文中内容的时效性。

JavaScript 是一种弱类型的语言,JS 中变量相对于强类型语言的变量要更加灵活,因为它没有数据类型的限制,你可以放心的赋予变量任何数据类型的值。但是数据本身和各种运算符是有类型的,如果运算时,被运算符发现数据类型不匹配,它会自动进行类型转换。如果不知道类型转换的规则,可能会得到意想不到的结果。

扫盲

在 ES5 中一共有六种数据类型:

他们又可以细分为两种,一种是原始类型(primitive type)的数据:

另一种是复合类型(complex type)的数据

复合类型还可以细分为三种

显式转换

显式转换是指通过调用特定的方法,将数据转换成特定的类型。

转换 number 类型

JavaScript 中主要有三种方法可以将其他类型的数据显示转换成数字类型:

  1. Number构造函数
  2. parseInt
  3. parseFloat

他们都可以将其他数据类型的值转换为数字或者NaN

原始类型值的转换规则

inputNumberparseIntparseFloat
数字不转换向下取整不转换
字符串数字/NaN数字/NaN数字/NaN
布尔值0/1NaNNaN
null0NaNNaN
undefinedNaNNaNNaN

对数字的转换

Number(3.1415926) // > 3.1415926
parseInt(3.1415926) // > 3
parseFloat(3.1415926) // > 3.1415926

parseInt  在转换数字时会向下取整,损失精度。Number  和  parseFloat  转换结果不变。 在转换数字时,如果数字是以 0 开头,那么将数字转换为 8 进制,如果数字是以 0x 开头的,那么将数字转换为 16 进制。

Number(010) // > 8
parseInt(010) // > 8
parseFloat(010) // > 8

Number(0x10) // > 16
parseInt(0x10) // > 16
parseFloat(0x10) // > 16

对字符串的转换

Number("") // > 0
parseInt("") // > NaN
parseFloat("") // > NaN

Number("Hello 123") // > NaN
parseInt("Hello 123") // > NaN
parseFloat("Hello 123") // > NaN

Number("123 Hello") // > NaN
parseInt("123 Hello") // > 123
parseFloat("123 Hello") // > 123

Number  转换字符串时,会把字符串当作一个整体,如果字符串是空的,那么返回0,如果字符串字符串不是空的,且字符串中有一个字符不是数字,那么转换结果就是  NaN。 parseInt  与  parseFloat  在转换字符串时,会一个字符一个字符的转换,如果字符串开头存在数字,那么返回数字,如果字符串开头不是数字,那么直接返回  NaN。 在用parseInt进行转换时,如果字符串是以0x开头的数字,那么将字符串转换为 16 进制。

parseInt("0x0010Hello") // > 16
parseFloat("0x0010Hello") // > 0

对布尔的转换

Number(true) // > 1
parseInt(true) // > NaN
parseFloat(true) // > NaN

Number(false) // > 0
parseInt(false) // > NaN
parseFloat(false) // > NaN

Number  在转换布尔值的时候, 如果布尔值为  true  返回  1,否则返回  0 。 parseIntparseFloat  转换布尔值总是返回  NaN

对 null 的转换

Number(null) // > 0
parseInt(null) // > NaN
parseFloat(null) // > NaN

Number  转换 null 值的结果为  0parseIntparseFloat  转换 null 的值为  NaN

对 undefined 的转换

Number(undefined) // > NaN
parseInt(undefined) // > NaN
parseFloat(undefined) // > NaN

NumberparseIntparseFloat  转换 undefined 的值为  NaN

复合类型值的转换规则

JS 中将复合类型转换为数字的规则比较复杂,主要分为两个阶段:

1.ToPrimitive 阶段,JS 引擎首先执行 ToPrimitive 方法,将对象转换为原始类型的数据,在这个方法中首先执行对象的 valueOf 方法,如果此方法返回值是原始类型,则直接进入第二阶段。如果不是,则继续执行该对象的 toString 方法,如果此方法返回原始类型的数据,则进入第二阶段,否则抛出异常。 2.ToNumber  阶段,按照原始类型的规则转换数据。

1.valueOf  返回原始值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return 1
  },
  toString: function () {
    console.log("-- 2 --")
    return {}
  },
}

console.log(Number(obj))
// > -- 1 --
// > 1

2.toString  返回原始值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return {}
  },
  toString: function () {
    console.log("-- 2 --")
    return 3
  },
}

console.log(Number(obj))
// > -- 1 --
// > -- 2 --
// > 3

3.不返回原始值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return {}
  },
  toString: function () {
    console.log("-- 2 --")
    return {}
  },
}

console.log(Number(obj))
// > -- 1 --
// > -- 2 --
// > Uncaught TypeError: Cannot convert object to primitive value

转换 object

;({})
  .valueOf()(
    // > {}
    {}
  )
  .toString() // > "[object Object]"

所以 object 转换数字的结果为  Number("[object Object]") = NaN

转换 array

[].valueOf() 		// > []
[].toString() 		// > ""

[1].valueOf() 		// > [1]
[1].toString() 		// > "1"

[1,2].valueOf() 		// > (2) [1, 2]
[1, 2].toString() 		// > "1,2"

此处可以根据数组toString的结果,按照转换字符串的规则进行转换。

转换 function

;(function () {})
  .valueOf()(
    // > ƒ (){}
    function () {}
  )
  .toString() // > "function (){}"

所以 function 转换数字的结果为  Number("function (){}") = NaN

转换 string 类型

JavaScript 中通过使用String构造函数, 将其他类型的值转换为字符串

原始类型值的转换规则

原始类型转换 string 的规则比较简单:

String(3.14) // > "3.14"
String("Hello") // > "Hello"
String(true) // > "true"
String(false) // > "false"
String(null) // > "null"
String(undefined) // > "undefined"

复合类型值的转换规则

JS 中将复合类型转换为字符串与转换数字的规则比较接近,区别点主要是在第一阶段求原值过程,在转换数字时,ToPrimitive方法会首先调用对象的  valueOf方法,其次执行toString方法, 在转换字符串时,则先调用对象的toString方法,其次执行valueOf方法。

1.ToPrimitive 阶段,JS 引擎首先执行 ToPrimitive 方法,将对象转换为原始类型的数据,在这个方法中首先执行对象的 toString 方法,如果此方法返回值是原始类型,则直接进入第二阶段。如果不是,则继续执行该对象的 valueOf 方法,如果此方法返回原始类型的数据,则进入第二阶段,否则抛出异常。 2.ToString  阶段,按照原始类型的规则转换数据。

以下代码反应了这一过程: 1.toString  返回原值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return {}
  },
  toString: function () {
    console.log("-- 2 --")
    return 1
  },
}
console.log(String(obj))
// > -- 2 --
// > 1

2.valueOf  返回原值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return ""
  },
  toString: function () {
    console.log("-- 2 --")
    return {}
  },
}
console.log(String(obj))
// > -- 2 --
// > -- 1 --
// >

3.都不返回原值

var obj = {
  valueOf: function () {
    console.log("-- 1 --")
    return {}
  },
  toString: function () {
    console.log("-- 2 --")
    return {}
  },
}
console.log(String(obj))
// > -- 2 --
// > -- 1 --
// > Uncaught TypeError: Cannot convert object to primitive value

转换 object 为 string

;({}).toString() // > "[object Object]"
String({}) // > "[object Object]"

转换 array 为 string

;[1, 2, 3].toString() // > "1, 2, 3"
String([1, 2, 3]) // > "1, 2, 3"

转换 function 为 string

function fn() {
  console.log("--- ^ ---")
}
fn.toString() // > "function fn() {console.log('--- ^ ---')}"
String(fn) // > "function fn() {console.log('--- ^ ---')}"

转换 boolean 类型

通过使用Boolean构造函数,将其他类型的数据显示的转换成布尔值,布尔类型的转换规则如下:

除 undefined、null、”、0(不区分正负)、NaN  这五个值之外,其他的值都是 true。

Boolean(undefined) // > false
Boolean(null) // > false
Boolean(0) // > false
Boolean("") // > false
Boolean(NaN) // > false

Boolean({}) // > true
Boolean([]) // > true
Boolean(function () {}) // > true

小结

1.以上就是 JavaScript 中数据类型的显示转换规则,其中number类型的转换规则相对复杂一些,在对 number 的转换过程中,parseIntparseFloat的规则要简单一些,简单的记忆方式是  parseIntparseFloat  只能转换数字或者以数字开头的字符串, 其他全部是NaNNumber构造函数可以转换数字或者数字字符串,如果字符串中含有除数字之外的其他字符,则返回NaN。在转换null''时,返回 0,其余都是NaN。 2.string需要注意的一点是,在转换复合类型时,在ToPrimitive阶段首先调用的是toString,其次才是valueOf。 3.boolean只需要记住五个特殊值即可。

移花接木 (隐式转换)

一元运算符  +、“

一元运算符+-可以将其他类型的值转换为数字,-转换的结果为负数。他们的转换规则与Number构造函数的转换规则相同。

;+3.14 - // > 3.14
  3.14 + // > -3.14
  "" - // > 0
  "" + // > -0
  "hello" - // > NaN
  "hello" + // > NaN
  true - // > 1
  true + // > -1
  false - // > 0
  false + // > -0
  null - // > 0
  null + // > -0
  undefined - // > NaN
  undefined + // > NaN
  {} - // > NaN
  {} + // > NaN
  [] - // > 0
  [] + // > -0
  [1] - // > 1
  [1] + // > -1
  [1, 2] - // > NaN
  [1, 2] + // > NaN
  function () {} - // > NaN
  function () {} // > NaN

一元运算符  !  取反

取反运算符一般用于将布尔值变为相反值。但是如果运算子不是布尔值,就会隐式的将它变为布尔值,转变规则与Boolean构造函数相同。

!null       // > true
!''         // > true
!0          // > true
!undefined  // > true
!NaN        // > true

其他的都为 false

小技巧:可以使用两个!,达到与 Boolean()  相同的效果。

!!null // > false
!!"" // > false
!!0 // > false
!!undefined // > false
!!NaN // > false

二元运算符

+ 号运算符

二元运算符  +  的隐式转换规则比较复杂, 因为它可以完成两种操作,一是加法运算,二是字符串的拼接。它的运算规则如下:

1.如果运算子中包含对象,则先按照转换数字的方式将对象转换为原始值(这里只是转换为原始值,不会直接转成数字),既先执行 valueOf 方法,再执行 toString 方法。如果对象是 Date 类型,则先执行 toString 方法。 2.当运算子都为原始类型之后,若有一个运算子为 string 类型,则执行字符串拼接 (这里分两种情况,如果运算符左边是数字,则将其他原始类型的值也转换为数字,否则执行字符串拼接)。 3.否则将其他类型的值转换为数字,执行加法运算。

1.如果+运算符两边的运算子都是数字,那么不转换类型,执行加法运算。

3 + 3 // 6

2.如果+运算符右边运算子的原始类型不是数字,那么自动地将他们转换为数字。如果右边是字符串, 那么就会将两个算子拼接在一起。

3 + "5" // 3 + '5' = '3' + '5' = '35'
3 + true // 3 + Number(true) = 3 + 1 = 4
3 + null // 3 + Number(null) = 3 + 0 = 3
3 + undefined // 3 + Number(undefined) = 3 + NaN = NaN

"3" + 4 + 5 // 345
3 + 4 + "5" // 75

3.如果运算子中存在对象

3 + {} // 3 + "[Object Object]" = "3[Object Object]"
3 + [] // 3 + "" = "3"

{
}
;+{} // "[object Object][object Object]"
{
}
;+[](
  // {}; +[] = +0 = 0
  {}
) + [] // "[object Object]" + "" = "[object Object]"

(减)、 *(乘)、 /(除)、 %(求余)

3 - "2" // > 3 - Number('2') = 1
3 * null // > 3 * Number(null) = 3 * 0 = 0
3 / undefined // > 3 / Number(undefined) = 3 / NaN = NaN
3 % [2] // > 3 % Number([2]) = 3 % 2 = 1

比较运算符

比较运算符有><>=<, 他们的转换规则如下:

1.如果两个运算子都为字符串,那么按照字典顺序比较。 2.否则将其他的数据类型都转换为数字,再比较。

3 > 2 // true
3 > "5" // 3 > 5   = false
3 > "q" // false

3 > null // 3  > Number(null) => 3 > 0  =  true
3 > undefined // false

任何值与 NaN 相比较,结果都是 NaN。

1 > NaN // false
1 <= NaN // false

"1" > NaN // false
"1" <= NaN // false

NaN > NaN // false
NaN <= NaN // false

相等运算符

JavaScript 中有两种判断是否相等的运算符,第一个是 JS 遗留下的糟粕  ==, 第二个是严格相等运算符  ===。 他们的区别的严格相等不会进行隐式转换。一旦两个运算子的类型不相同,那么就返回false

为什么说 == 是糟粕呢? 我认为  ==  进行的很多隐式转换都不是很合理, 如果使用不当,结果会适得其反。

2 == true // > false

以下是  ==  的简单转换规则: 1.nullundefined  与其他任何值的比较结果都是 false, 除非他们两个互相比较。

null == 3 // > false
undefined == 3 // > false

null == NaN // > false
undefined == NaN // > false

null == "" // > false
undefined == "" // > false

null == false // > false
undefined == false // > false

null == undefined // > true

2.在比较其他的原始类型时,会转换为数字。

1 == true // > 1 == Number(true)   => true
2 == true // > 2 == Number(true)   => false

"" == 0 // > true
"" == false // > Number('') === Number(false)   true

3.原始类型与复合类型比较时,会将复合类型转换成对应的原始类型。

;(([1] == (1)[1]) == // > Number([1]) == 1  => true
  "1"[1]) == // > String([1]) == Number('1') => true
  true // > Boolean([1]) == true => true

一道很扯淡的题目

0 == null // > false
0 > null // > false
0 >= null // > true   why?

第一行  null  与除undefined以外任何值相比较的结果都是  false

第二行  0 > null, 因为运算子没有字符串, 所以将其他类型的值转换为数字, 所以

=> 0 > Number(null) => 0 > 0 => false

第三行  0 >= null, 还是没有字符串,转换为数字,

=> 0 >= Number(null) => 0 >= 0 => true

其他操作符的小知识

逗号运算符

,  逗号运算符总是返回最后一个表达式的结果。

for (var i = 1, j = 1; i < 10, j < 5; i++, j++) {}

console.log(i, j) // > 5 5

&&且运算符

且运算符的运算规则如下:

1.如果第一个运算子的值为 false, 则返回第一个运算子的值,且不再对第二个运算子进行计算。 2.否则返回第二个运算子的值。

0 && 3 // > 0

3 && 0 // > 0

||或运算符

或运算符的运算规则如下:

1.如果第一个运算子的值为 true, 则返回第一个运算子的值,且不再对第二个运算子进行计算。 2.否则返回第二个运算子的值。

0 || 3 // > 3
3 || 0 // > 3

~否运算符

连续使用两个否运算符,进行向下取整,这是所有取整方法中最快的一种。

~~3.1415926 // > 3

~~3.9999 // > 3

^异或运算符

异或运算符常用于不借助第三方变量,交换两个变量的值。

var a = 9
var b = 5

a = a ^ b
b = a ^ b
a = a ^ b

console.log(a, b) // > 5 9

EOF

参考资料: 1.阮一峰 : JavaScript 标准参考教程