[ECMA-262]-深入探究隐式类型转换

JavaScript是弱类型的语言,在很多情况下会涉及到隐式类型转换,这种类型转换在JavaScript引擎内部进行。通过参考ECMA-262,我们对几种常见的隐式类型转换在引擎内部的执行逻辑进行分析。

ToPrimitive( input [ , PreferredType ] )

这个抽象操作有一个参数input(表示需要进行类型转换的数据),还有一个可选参数PreferredType(表示期望转换成的类型)。

如果input是原始值,那么就直接返回输入本身。

如果input是对象,那么进行如下的操作:

  1. 如果没有参数PreferredType,那么设置hint为“default”。

  2. 如果参数PreferredType是String或者Number,那么分别设置hint为“String”或者“Number”。

  3. 判断input有没有@@toPrimitive方法(是一个用于将对象转成原始值的方法)

    • 如果有,则调用该方法,并返回result,如果result不是对象类型,那么result就是最终的转换结果,结束,否则抛出TypeError异常。
    • 如果没有,继续步骤4。
  4. 如果hint是”default“,那么设置hint为”Number“。

  5. 调用OrdinaryToPrimitive方法,返回OrdinaryToPrimitive(input, hint)的值。

    • 确保hint是一个字符串,且只能是“string”或“number”。
    • 如果hint是”String“,设置methodNames 为 < “toString”, “valueOf” >,否则,设置methodNames 为 < “valueOf”, “toString” >。
    • 如果methodNames中的方法是可以调用的方法,那么就依次按顺序调用,如果返回值不是Object,那么OrdinaryToPrimitive直接返回这个返回值,结束。否则继续调用下一个方法。
    • 如果遍历methodNames中的的所有方法,都没有符合条件的返回值,那么抛出TypeError异常。

    注意,如果没有设置PreferredType 的值,那么会默认其为Number。但是,对象可能会通过重写@@toPrimitive方法来改变这一默认行为。Date对象和Symbol对象就改变了这一默认操作。Date对象在没有设置PreferredType的情况下,默认其为String。

ToNumber ( argument )

该操作遵循的规则如下表所示:

Argument Type Result
Undefined Return NaN.
Null Return +0.
Boolean If argument is true, return 1. If argument is false, return +0.
Number Return argument (no conversion).
String See grammar and conversion algorithm below.
Symbol Throw a TypeError exception.
Object Apply the following steps:
1. Let primValue be ToPrimitive(argument, hint Number).
2. Return ToNumber(primValue).

其中对String和Object的转换需要详细分析。

ToNumber(String)

如果字符串不能解释为字符串数字字面量(StringNumericLiteral),那么结果为NaN。

StringNumericLiteral的定义如下:

  1. StrWhiteSpace(opt)

  2. StrWhiteSpace(opt) StrNumericLiteral StrWhiteSpace(opt)

    • StrWhiteSpace的定义:StrWhiteSpaceChar StrWhiteSpace(opt)

      • StrWhiteSpaceChar的定义:
        • WhiteSpace(<LF> <CR> <LS> <PS>)
        • LineTerminator(<TAB> <VT> <FF> <SP> <NBSP> <ZWNBSP> <USP>)
    • StrNumericLiteral的定义:

      • StrDecimalLiteral

        定义:

        • StrUnsignedDecimalLiteral

          定义:

          Infinity
          DecimalDigits . DecimalDigits(opt) ExponentPart(opt)
          . DecimalDigits ExponentPart(opt)
          DecimalDigits ExponentPart(opt)

        • +StrUnsignedDecimalLiteral

        • -StrUnsignedDecimalLiteral

      • BinaryIntegerLiteral

      • OctalIntegerLiteral

      • HexIntegerLiteral

通过以上的定义我们可以知道,字符串数字字面量(StringNumericLiteral)和数字字面量(NumericLiteral)有以下几点不同之处:

  1. StringNumericLiteral的开头和结尾可能包含着空白和行终止符。

    例如:

    1
    2
    Number('123\n');//123
    Number(' 123 ');//123
  2. StringNumericLiteral的十进制数前面可以有任意多个0,而NumericLiteral有前导零时,代表八进制数。

    例如,表达式Number("012") === 012的结果为false,因为前者等于12,而后者是八进制数,转换成十进制后是10.

  3. StringNumericLiteral可以添加+``-来声明符号位。

  4. StringNumericLiteral在空或只包含空白字符的时候,转换结果为+0。

    例如:

    1
    2
    3
    4
    Number('\n'); // 0 行分隔符系列
    Number('');// 0,空白字符系列
    //注意’\0‘并不包含在StrWhiteSpace的定义中
    Number('\0');//NaN
ToNumber(Object)

这里要用到之前分析的ToPrimitive,先把对象类型值转换为原始值,然后在调用ToNumber函数

1
2
primValue = ToPrimitive(argument, hint Number);
Return ToNumber(primValue);

ToString( argument )

该操作遵循的规则如下表所示:

Argument Type Result
Undefined ‘undefined’
Null ‘null’
Boolean ‘true’ or ‘false’
Number NumberToString(argument)
String argument
Symbol Throw a TypeError exception
Object Apply the following steps:
1. Let primValue be ToPrimitive(argument, hint String).
2. Return ToString(primValue).

ToBoolean

Argument Type Result
Undefined Return false.
Null Return false.
Boolean Return argument.
Number If argument is +0, -0, or NaN, return false; otherwise return true.
String If argument is the empty String (its length is zero), return false; otherwise return true.
Symbol Return true.
BigInt If argument is 0n, return false; otherwise return true.
Object Return true.