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

Posted 原罪

tags:

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

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

类型 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()。
  3. 使用减法或者Number(...)包装时,preferedType=number,即优先调用.valueOf()
[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()

ToNumber

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

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转换为基本类型

  • 加法(+)运算,preferedType是默认值
  • 减法(-)运算,preferedType是Number
// {}.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() {}"

字符串 + 任意值,会被处理为字符串的拼接

这里的任意值都是指基本类型,因为对象会先执行ToPrimitive变成基础类型

1 + "1" === "11"
1 + 1 === 2
1 + 1 + "1" === "21"
"1" + 1 === "11"
"1" + "1" === "11"
1 + "1" + 1 === "111"
"1" + false === "1false"
"1" + null === "1null"
"X" + undefined + false === "Xundefinedfalse"

非字符串 + 非字符串,两边都会先ToNumber

这里的非字符串都是指基本类型,因为对象会先执行ToPrimitive变成基础类型

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"
null + null === 0

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

任意值 - 任意值,一律执行ToNumber,进行数字运算。

此时的 preferedType === 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

// Date的默认preferedType === string
var date = new Date();
date.toString = () => \'str\';
date.valueOf = () => 123;

date + 1 === \'str1\';
date - 1 = 122;

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

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

+ new Date() === 1623251729456


// 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

// 吃根香蕉

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

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

隐式类型转换

深入浅出JavaScript中的隐式转换

scala中隐式转换之总结

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

JavaScript中数据类型转换总结