wulang8353 / DO-THE-JS-BETTER

:trollface: 我的真阳为至宝,岂肯轻与你这粉骷髅——JS :tada:

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

我觉得写的还蛮清楚的,JS数据类型转换从来都是一把梭

wulang8353 opened this issue · comments

数据类型转换

每日一话:我知道为什么恋人之间喜欢接吻,不然喜欢就会流出来了

前言

先来个问题: [] === ![] 这个表达式的结果是true还是false呢?

这个问题是校招时对于基础知识考察中的一个很常见的问题,当时死记硬背下来为了过关,但其大致原理也是得过且过了。在此写下这篇,虽然校招已过,但也算是了曾经的一个盲区了。

JS是一种动态型语言,变量定义时无需声明类型,可以在运算时自动转换数据类型

var m = n ? 'hello' : 1

变量 m 的类型由变量 n 来确定, 若 n 为true时,m 是一个字符串;若 n 为false时,m 是一个数值。

虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。

当运算符发现元素类型不符,就会自动转换数据类型。最为常见的就是减法运算符,若两侧有一方不为数值,则自动转为数值。

JS类型转换强制转换也叫显性转换,指调用函数去转换数据类型。还要一种类型转换叫自动转换,也叫做隐形转换。顾名思义,这种转换是看不见的,是代码在运算时为了统一类型自动转换的。

强制转换

强制转换主要指使用Number、String和Boolean三个函数,将各种类型的值转换成数值、字符串或者布尔值。

JS数据类型分为原始类型与对象类型

Number()

| 原始类型

// <数值>
// 转换后还是原来的值
Number(123) // 123

// <字符串>
// 如果可以被解析为数值,则转换为相应的数值
Number('123') // 123

// 如果不可以全部被解析为数值,返回 NaN
Number('123abc') // NaN

// 空字符串转为0
Number('') // 0

// <布尔值> 
// true -> 1,false -> 0
Number(true) // 1
Number(false) // 0

// <undefined> 
// 转成 NaN
Number(undefined) // NaN

// <null> 
// null转成0,这个是重点
Number(null) // 0

// parseInt函数不似Number整体转换,而逐个解析直到不能解析
parseInt('1 cats') // 1
Number('2 cats') // NaN

| 对象

简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组

Number({a: 1}) // NaN - 对象返回NaN
Number([1, 2, 3]) // NaN - 多值数组返回NaN
Number([5]) // 5  单值数组返回该值

之所以会这样,是因为Number背后的转换规则是分三步走的

1、调用对象自身 valueOf方法。若能返回原始数据类型的值,则直接使用Number函数(规则如上)。若返回对象看2

2、调用对象自身toString方法。若能返回原始数据类型的值,则直接使用Number函数(规则如上)。若返回对象看3

3、都返回对象了,都报错了,就直接返回NaN

var obj = {name: 'Tim'};
Number(obj) // NaN

// 等同于
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
  // obj为对象,obj.toString() = "[object Object]"
} else {
  Number(obj.valueOf()); 
}

首先调用obj.valueOf方法, 结果返回对象本身。继续调用obj.toString方法,这时返回字符串[object Object]。对这个字符串使用Number函数,得到结果NaN

对象的valueOf方法一般直接返回对象或者本身,而toString方法返回对象的类型字符串(比如[object Object]),返回数组的字符串形式

Number({}) // NaN 

Number([1])// [1].toString() -> '1' -> Number('1') = 1

// valueOf和toString方法都可以自定义
Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// Number({a:1})  2
// 对象先调用valueOf再调用toString,所以返回2

String()

String函数可以将任意类型的值转化成字符串

| 原始类型

String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"

| 对象

如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
String([1]) // "1"

转换原理与Number()刚好相反,先调用toString()再调用valueOf():

1、调用对象自身toString方法。若能返回原始数据类型的值,则直接使用String函数(规则如上)。若返回对象看2

2、调用对象自身 valueOf方法。若能返回原始数据类型的值,则直接使用String函数(规则如上)。若返回对象看3

3、都返回对象了,都报错了,就直接返回NaN

String({a: 1})
// "[object Object]"

// 等同于
String({a: 1}.toString())
// "[object Object]" 直接返回原始类型,不用再调用valueOf方法

Boolean()

Boolean函数可以将任意类型的值转为布尔值

它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true。所有对象(包括空对象)的转换结果都是true。

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false

自动转换

自动转换的基础就是默认强制转换,遇到以下情况时,JavaScript 会自动转换数据类型

  • 1 不同类型的数据进行逻辑运算

加法运算: 当一个值为字符串,另一个值为非字符串,则后者转为字符串

'1' + 2 // '12'
'1' + true // "1true"
'1' + false // "1false"
'1' + {} // "1[object Object]"
'1' + [] // "1"
'1' + function (){} // "1function (){}"
'1' + undefined // "1undefined"
'1' + null // "1null"

非加法运算符:自动转成数值

'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN
  • 2 对非布尔值类型的数据求布尔值
// 写法1
if ('abc') {
  console.log('hello')
}

// 写法2
![1] === false

// 写法3
!! expression

// 写法4
expression ? true : false
  • 3 对非数值类型的值使用一元运算符(即+和-)变为数值
+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN
+ true // 1
- false // 0

回到[] === ![]这个问题,答案为 true

首先了解一下运算符的优先级:

解析过程:

1、由上图可以看到 ! 的优先级较 === 高。首先计算 ![],因为 [] 属于对象,所以 Boolean([])返回true,![] 返回 false

2、[] === false, 一方操作数为对象,另一方为布尔值。 对于数组而言,除了日期对象,都是对象到数字的转换

3、[].valueOf() -> [] -> [].toString() -> '' -> Boolean('') -> false

这里等式左侧之所以不直接理解成 Boolean([]),是因为除了求布尔值的四种写法外,对象的转换都是要调用toString和valueOf方法

3、 false === false // true

若 [] === [] 或者 {} === {} 比较的是内存地址,所以都返回 false

思考一下: {} === !{}

1、! 的优先级较 === 高,首先计算 !{},因为 {} 属于对象,所以 Boolean({}) 返回true,!{}返回 false

2、{} === false, 一方操作数为对象,另一方为布尔值。 对于对象而言,都是对象到字符串的转换

3、{}.toString() -> "[objext obejct]" === false // false

总结

两个操作数进行运算,无论如何转换最终目标都是转换成原始类型数据进行运算

我们总结一下类型转换的规则:

1、 undefined == null,结果是true

2、 加法运算: 当一个值为字符串,另一个值为非字符串,则后者转为字符串; 非加法的运算转为数值

3、 对4种类型的数据求布尔值,直接使用Boolean方法。且!优先级高

4、 当有一方操作数为对象类型时,若是数组,一般预期转为数值,调用顺序valueOf() -> toString();若是对象,一般预期转为字符串,调用顺序toString() -> valueOf();