关于JS隐式类型转换的完整总结

Posted 前端开发博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于JS隐式类型转换的完整总结相关的知识,希望对你有一定的参考价值。

点击上方“前端开发博客”,选择“设为星标”

回复“2”加入前端群

不管是在技术聊天群还是论坛里还是在面试官的卷子里,总能碰到 [] + {} == ? 之类的问题,如果你不了解其中的原理,那么就插不上话,只能眼睁睁地等大佬解答了。

类型 Type

说到底还是JS类型转换的问题,首先我们先温习一下JS的8种内置类型:

  • Number

  • String

  • Boolean

  • Null

  • Undefined

  • Object

  • Symbol (ES2015)

  • BigInt (ESNext stage 4)

是不是感觉还有Function,毕竟能用typeof获取到?不,函数、数组都是Object的子类型。

类型分为基本类型复合类型两种,除了对象,其它都是基本类型。

To Primitive

发音:[ˈprɪmətɪv]
结构:toPrimitive(input: any, preferedType?: 'string' |'number')
作用:内部方法,将任意值转换成原始值

转换规则:

  1. 如果是基本类型,则不处理。

  2. 调用valueOf(),并确保返回值是基本类型。

  3. 如果没有valueOf这个方法或者valueOf返回的类型不是基本类型,那么对象会继续调用toString()方法。

  4. 如果同时没有valueOf和toString方法,或者返回的都不是基本类型,那么直接抛出TypeError异常。

注意:如果 preferedType=string,那么2、3顺序调换。

接着,我们看下各个对象的转换实现

对象valueOf()toString()默认 preferedType
Object原值"[object Object]"Number
Function原值"function xyz() {...}"Number
Array原值"x,y,z"Number
Date数字"Sat May 22 2021..."String
  1. 数组的toString()可以等效为join(","),遇到null, undefined都被忽略,遇到symbol直接报错,遇到无法ToPrimitive的对象也报错。

  2. 使用模板字符串或者使用String(...)包装时,preferedType=string,即优先调用 .toString()。

[1, null, undefined, 2].toString() === '1,,,2';

// Uncaught TypeError: Cannot convert a Symbol value to a string
[1, Symbol('x')].toString()

// Uncaught TypeError: Cannot convert object to primitive value
[1, Object.create(null)].toString()

To Number

一些特殊值转为数字的例子,等下要用到

Number("0") === 0;
Number("") === 0;
Number("   ") === 0;
Number("\\n") === 0;
Number("\\t") === 0;
Number(null) === 0;
Number(false) === 0;

Number(true) === 1;

Number(undefined); // NaN
Number("x"); // NaN

加减法 +-

加减法运算中遵循了一些隐式转换规则:

遇到对象先执行ToPrimitive转换为基本类型,然后按照基本类型的规则处理

// {}.toString() === "[object Object]"
1 + {} === "1[object Object]"

// [2, 3].toString() === "2,3"
1 + [2, 3] === "12,3"
[1] + [2, 3] === "1,2,3"

function test() {}
// test.toString() === "function test() {}"
10 + test === "10function test() {}"

加法过程中,遇到字符串,则会被处理为字符串拼接

上面的对象最后也都转成了字符串,遵循本条规则。接着来几个纯字符串的例子

1 + "1" === "11"
1 + 1 === 2
1 + 1 + "1" === "21"
"1" + 1 === "11"
"1" + "1" === "11"
1 + "1" + 1 === "111"

减法操作时,一律需要把类型转换为Number,进行数学运算

3 - 1 === 2
3 - '1' === 2
'3' - 1 === 2
'3' - '1' - '2' === 0

// [].toString() => "" => Number(...) => 0
3 - [] === 3

// {}.toString() => "[object Object]" => Number(...) => NaN
3 - {} // NaN

加法操作时,遇到非字符串的基本类型,都会转Number

1 + true === 2
1 + false === 1
1 + null === 1
1 + null + false + 1 === 2
1 + undefined // NaN
1 + undefined + false // NaN
1 + undefined + [1] === "NaN1"
1 + undefined + "1" === "NaN1"

// 1 + false
1 + ![] === 1
1 + !{} === 1
!{} + !{} === 0

+ x 和 一元运算 +x 是等效的(以及- x),都会强制转换成Number

+ 0 === 0
- 0 === -0

// 1 + (+"1")
1 + + "1" === 2

// 1 + (+(+(+["1"])))
1 + + + + ["1"] === 2

// 1 + (-(+(-[1])))
1 + - + - [1] === 2

// 1 - (+(-(+1)))
1 - + - + 1 === 2

1 - + - + - 1 === 0

// 1 + [""]
1 + + [""] === 1

// ["1", "2"].toString() => "1,2" => Number(...) => NaN
1 + + ["1", "2"] // NaN

// 吃根香蕉????
// "ba" + (+undefined) + "a" => "ba" + NaN + "a"
("ba" + + undefined + "a").toLowerCase() === "banana"

回到一开始抛出的问题[] + {},这样太简单了吧?

[].toString() === "";
{}.toString() === "[object Object]";

[] + {} === "[object Object]";

{} 在最前面时可能不再是对象

不是对象是什么?是你的八块腹肌?别急,看看经典的例子

{} + [] === 0;
{ a: 2 } + [] === 0;

这啥玩意?说好的"[object Object]"呢?

好吧,这是{}其实代表的是代码块,最后就变成了+ [],根据前面的原则,数组先被转换成字符串"",接着因为+x的运算,字符串被转成数字0

那 { a: 2 } 总该是对象了吧?其实这时候a不是代表对象属性,而是被当成了标签(label),标签这东西IE6就已经有了。所以如果我们写成对象是会报错的,逗号要改成分号才能通过编译。

// Uncaught SyntaxError: Unexpected token ':'
{ a: 2, b: 3 } + []

// 分号OK
{ a: 2; b: 3 } + [] === 0;

⚠️注意:在 Node >= 13.10.0 的版本,{}被优先解释为空对象,仅在非对象结构的情况才会被认为是代码块。

// nodeJs >= 13.10.0 的运行结果

{} + [] === "[object Object]";
{ a: 2 } + [] === "[object Object]";
{ a: 2, b: 3 } + [] === "[object Object]";

// 注意是分号,当成代码块
{ a: 2; b: 3 } + [] === 0;
// 有JS语句或者表达式,当成代码块
{ var a = 1; } + [] === 0;
{ ; } + [] === 0;
{ 123 } + [] === 0;
{ 1 + 2 } + [] === 0;

定论还是下的太早了,我们还是有办法让引擎优先处理成代码块

// 所有node版本

;{} + [] === 0;
;{ a: 2 } + [] === 0;

// Uncaught SyntaxError: Unexpected token ':'
;{ a: 2, b: 3 } + [];

加个分号?有趣。


在使用这个刺激的规则前,你需要记住的是,代码块 {} 必须在最前面,否则它就是对象,但也有特例(分号)。

console.log({} + []); // "[object Object]"
alert({} + []); // "[object Object]"

function test(x) { console.log(x); }
test({} + []); // "[object Object]"

var result = {} + [];
console.log(result); // "[object Object]"

({}) + [] === "[object Object]"

!{} // !对象 => false

{} // 对象
{ a: 2 } // 对象
{ a: 2, b: 3 } // 对象

{ a: 2; b: 3 } // 代码块
;{ a: 2 } // 代码块
{ a: 2 }; // 代码块

symbol不能加减

如果在表达式中有symbol类型,那么就会直接报错。比如1 + Symbol("x")报错如下:

Uncaught TypeError: Cannot convert a Symbol value to a number

宽松相等 ==

相等于全等都需要对类型进行判断,当类型不一致时,宽松相等会触发隐式转换。下面介绍规则:

对象与对象类型一致,不做转换

{} != {}
[] != {}
[] != []

对象与基本类型,对象先执行ToPrimitive转换为基本类型

// 小心代码块
"[object Object]" == {}
[] == ""
[1] == "1"
[1,2] == "1,2"

数字与字符串类型对比时,字符串总是转换成数字

"2" == 2
[] == 0
[1] == 1
// [1,2].toString() => "1,2" => Number(...) => NaN
[1,2] != 1

布尔值先转换成数字,再按数字规则操作

// [] => "" => Number(...) => 0
// false => 0
[] == false

// [1] => "1" => 1
// true => 1
[1] == true

// [1,2] => "1,2" => NaN
// true => 1
[1,2] != true

"0" == false
"" == false

null、undefined、symbol

null、undefined与任何非自身的值对比结果都是false,但是null == undefined 是一个特例。

null == null
undefined == undefined
null == undefined

null != 0
null != false

undefined != 0
undefined != false

Symbol('x') != Symbol('x')

对比 < >

对比不像相等,可以严格相等(===)防止类型转换,对比一定会存在隐式类型转换。

对象总是先执行ToPrimitive为基本类型

[] < [] // false
[] <= {} // true

{} < {} // false
{} <= {} // true

任何一边出现非字符串的值,则一律转换成数字做对比

// ["06"] => "06" => 6
["06"] < 2   // false 

["06"] < "2" // true
["06"] > 2   // true

5 > null     // true
-1 < null    // true
0 <= null    // true

0 <= false   // true
0 < false    // false

// undefined => Number(...) => NaN
5 > undefined // false

To Boolean

既然是总结,那么可能还要讲一下布尔值的隐式转换。这个还是比较常见的,我们来看下有哪些地方会使用到:

  • if(...)

  • for(;...;)

  • while(...)

  • do while(...)

  • ... ? :

  • ||

  • &&

既然知道会转换,那么什么值是真值,什么值是假值呢?换个思路,假值以外都是真值。看看哪些是假值:

  • undefined

  • null

  • false

  • +0

  • -0

  • NaN

  • ""

还有吗?


欢迎纠正错误,欢迎学习交流收藏。我是原罪,一个极客。

关于本文

作者:原罪

https://segmentfault.com/a/1190000040048164

推荐阅读

Javascript 中数据类型那些可能会中招的细节

END

关注下方「前端开发博客」,回复 “电子书”

领取27本精选电子书

❤️ 看完两件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我两个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注公众号「前端开发博客」,每周重点攻克一个前端面试重难点

如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

公众号也开始通过互动率推送了,互动少了可能就很晚或者收不到文章了。

大家点个在看,星标我的公众号,就可以及时获得推文。

点个在看少个Bug

以上是关于关于JS隐式类型转换的完整总结的主要内容,如果未能解决你的问题,请参考以下文章

关于JS隐式类型转换的完整总结

隐式类型转换

深入浅出JavaScript中的隐式转换

scala中隐式转换之总结

js隐式类型转换,预编译递归

JavaScript中数据类型转换总结