# 隐式类型转换

# 前言

在javascript中,除了显示的转换数据类型如调用 Number() Boolean() String()等方法外,有时候一些写法能够直接起到隐式类型转换,例如我们常写的1 + '2' === '12'这其中 + 的运算就发生了一个隐式类型转换。除此之外,再思考一下下面的答案,后面会讲讲隐式转换的过程自然而然这些问题就能解决了。

[] + [];
{} + {};
[] + {};
{} + [];

1 == true;
0 == '';
null == undefined;


const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('hello world!'); // 输出hello world
}

# 隐式类型转换

# ToPrimitive(input, PreferredType?: 'Number' | 'String')

PreferredType 只是一个转换标志,实际最后的转换结果并不一定就是该类型,但是转换的结果一定要是个原始值。(否则会报错), 在javascript中,默认设置为 Number ,除了 inputDate 类型,则设置为 String

PreferredTypeNumber时:

  • 输入值已经是原始值,返回它。
  • 调用输入值的 valueOf 方法,如果结果是原始值,返回它。
  • 调用输入值的 toString 方法,如果结果是原始值,返回它。
  • 最后的结果还不是原始值,抛出TypeError异常。

PreferredType为String时:

  • 输入值已经是原始值,返回它。
  • 调用输入值的 toString 方法,如果结果是原始值,返回它。
  • 调用输入值的 valueOf 方法,如果结果是原始值,返回它。
  • 最后的结果还不是原始值,抛出TypeError异常。

# 常见对象的valueO, toString方法结果

valueOftoString 方法一定都有实现,有些类型自己的原型有实现,最后顶层原型 Object.prototype 都有这两个方法的实现。

先看 valueOf 方法:

  • Number, String, Boolean这三种基本类型的构造函数,通过 valueOf 转换后会变成对应的原始值。Date类型调用 valueOf 方法返回时间戳。
var num = new Number('123');
num.valueOf(); // 123

var str = new String('12df');
str.valueOf(); // '12df'

var bool = new Boolean('fd');
bool.valueOf(); // true

var date = new Date();
date.valueOf(); // 1622535486108

  • 其它类型,调用 valueOf 都返回 this,即自己本身。
var arr = [1, 2, 3];
arr.valueOf() === arr; // true
var obj = {a: 1};
obj.valueOf() === obj; // true
var sym = Symbol();
sym.valueOf() === sym; // true
var bigint = 1n;
bigint.valueOf() === bigint; // true

再看看 toString 方法,下面是一些例子,都是一股脑的转成字符串。

var num = 1;
var num1 = new Number('123as');
var bool = true;
var str = 'bowlofnoodles';
var sym = Symbol();
var bigint = 1n;

var arr = [1, 2, 3];
var obj = {};
var date = new Date();
var func = function () {};
var reg = /reg/g;

// 注 都是返回字符串形式
console.log(
  num.toString(), // 1
  num1.toString(), // NaN
  bool.toString(), // true
  str.toString(), // bowlofnoodles
  sym.toString(), // 'Symbol()'
  bigint.toString(), // 1
  arr.toString(), // [1,2,3]
  obj.toString(), // [object Object]
  date.toString(), // Tue Jun 01 2021 16:26:10 GMT+0800 (中国标准时间)
  func.toString(), // function func() {}
  reg.toString() // /reg/g
);


# ToNumber

就是显示类型转换 Number() 的调用结果。

  • undefined -> NaN
  • null -> 0
  • 布尔值 -> true1false0
  • 字符串 -> 纯数字的字符串解析为对应数字,否则为NaN。例如'123' -> 123'qwe123' -> NaN
  • 数字 -> 该数字
  • 对象 -> 先进行ToPrimive(input, Number)转化到原始值,在进行ToNumber转化为数字
  • 其它不包含在上列的,一般都转化为NaN

# ToString

就是显示类型转换String()的调用结果。

  • undefined -> 'undefined'
  • null -> 'null'
  • 布尔值 -> true'true'false'false'
  • 字符串 -> 该字符串
  • 数字 -> 1 -> '1'
  • 对象 -> 先进行ToPrimive(input, String)转化到原始值,在进行ToString转化为字符串
  • 其它不包含在上列的,一般都转化为对应的字符串,例如NaN -> 'NaN'

# + 和 ==

# + 运算符

  • 运算符的左右两边存在不为原始类型的值,则都调用ToPrimive()转换为原型值
  • 运算符左右两边都为原始值之后,如果存在一个是字符串,则调用ToString(),双方转换为字符串,然后作字符串拼接。
  • 运算符左右两边都为原始值之后,如果两边都不是字符串,则双方调用ToNumber()转化为数字后进行正常的加法运算。

现在我们来回看一下上面问题:

  • [] + []:都不是原始值,转换为原始值,默认ToPrimitive(input, Number),则为 '' + '' = '';
  • {} + {}: 都不是原始值,转换为原始值,默认ToPrimitive(input, Number),则为 '[object Object]' + '[object Object]' = '\[object Object\]\[object Object\]';
  • [] + {}: 都不是原始值,转换为原始值,...,则为 '' + '[object Object]' = '[object Object]';
  • {} + []: 注意这里跟[] + {}本质应该一样,但是{}放前面被识别成了代码段,所以实际相同于 +[],相当于Number(\[\])的操作,得到0。如果我们希望得到正确的结果,可以这样写({}) + [] => '[object Object]'

# == 运算符

比较 x == y,主要区分xy类型相同时,和类型不相同时。

类型相同时,没有类型转换,主要注意NaN不与任何值相等,包括它自己,即NaN !== NaN

类型不相同时,

  1. xynullundefined两者中一个,返回true

  2. xyNumberString类型时,则转换为Number类型比较。

  3. Boolean类型时,Boolean转化为Number类型比较。

  4. 一个Object类型,一个StringNumber类型,将Object类型进行原始转换后,按上面流程进行原始值比较。

详细流程和规则如下:

1、若 Type(x) 与 Type(y) 相同, 则

    1* 若 Type(x) 为 Undefined, 返回 true。
    2* 若 Type(x) 为 Null, 返回 true。
    3* 若 Type(x) 为 Number, 则
  
        (1)、若 x 为 NaN, 返回 false。
        (2)、若 y 为 NaN, 返回 false。
        (3)、若 x 与 y 为相等数值, 返回 true。
        (4)、若 x 为 +0 且 y 为 −0, 返回 true。
        (5)、若 x 为 −0 且 y 为 +0, 返回 true。
        (6)、返回 false。
        
    4* 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。 否则, 返回 false。
    5* 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。 否则, 返回 false。
    6*  当 x 和 y 为引用同一对象时返回 true。否则,返回 false。
  
2、若 x 为 null 且 y 为 undefined, 返回 true。

3、若 x 为 undefined 且 y 为 null, 返回 true。

4、若 Type(x) 为 Number 且 Type(y) 为 String,返回比较 x == ToNumber(y) 的结果。

5、若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。

6、若 Type(x) 为 Boolean, 返回比较 ToNumber(x) == y 的结果。

7、若 Type(y) 为 Boolean, 返回比较 x == ToNumber(y) 的结果。

8、若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。

9、若 Type(x) 为 Object 且 Type(y) 为 String 或 Number, 返回比较 ToPrimitive(x) == y 的结果。

10、返回 false。

最后,我们看一下开头的那道题:

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}

if (a == 1 && a == 2 && a == 3) {
  console.log('hello world!'); // 输出hello world
}
  • 当执行a == 1 && a == 2 && a == 3 时,会从左到右一步一步解析,首先 a == 1,会进行上面第9步转换。ToPrimitive(a, Number) == 1

  • ToPrimitive(a, Number),按照上面原始类型转换规则,会先调用valueOf方法,avalueOf方法继承自Object.prototype。返回a本身,而非原始类型,故会调用toString方法。

  • 因为toString被重写,所以会调用重写的toString方法,故返回1,注意这里是i++,而不是++i,它会先返回i,再将i+1。故ToPrimitive(a, Number) = 1。也就是1 == 1, 此时i = 1 + 1 = 2

  • 执行完a == 1返回true,会执行a == 2,同理,会调用ToPrimitive(a, Number),同上先调用valueOf方法,再调用toString方法,由于第一步,i = 2此时,ToPrimitive(a, Number) = 2, 也就是2 == 2, 此时i = 2 + 1

  • 同上可以推导 a == 3 也返回true。故最终结果 a == 1 && a == 2 && a == 3 返回true

Last Updated: 6/25/2021, 6:35:25 AM