JavaScript中的类型转换

晓风晓浪 / 2023-08-28 / 原文

JavaScript在处理不同类型的值时非常灵活。如果JavaScript需要布尔值,而您提供了其他类型的值,JavaScript会根据需要进行转换。一些值(称为truthy值)会转换为true,而另一些值(称为falsy值)会转换为false。对其他类型也适用同样的原则:如果JavaScript需要一个字符串,它会将您提供的任何值转换为字符串。如果JavaScript需要一个数字,它会尝试将该值转换为数字(如果无法转换则为NaN)。

让我们看一些示例:

// => "10 objects":数值10被转换为字符串
10 + " objects"
// => 28:两个字符串都被转换为数字
"7" * "4"
// n == NaN;字符串"x"无法转换为数字
let n = 1 - "x";
// => "NaN objects":NaN被转换为字符串"NaN"
n + " objects"

1. 转换和等式。
JavaScript有两个用于测试两个值相等的运算符。一个是严格等号运算符(=),它不认为不同类型的值相等。然而,由于JavaScript的灵活类型转换,它还定义了宽松等号运算符(),它具有更灵活的等式概念。例如,以下比较都会返回true:

// => true:这两个值被认为是相等的
null == undefined
// => true:在比较之前,字符串被转换为数字
"0" == 0
// => true:在比较之前,布尔值被转换为数字
0 == false
// => true:在比较之前,两个操作数都被转换为0
"0" == false

但请记住,仅因为一个值可以转换为另一个值并不意味着它们相等。例如,如果在期望布尔值的上下文中使用undefined,它将被转换为false。然而,这并不意味着undefined == false。JavaScript运算符和语句期望不同类型的值,并对这些目标类型执行转换。if语句将undefined转换为false,但==运算符永远不会将其操作数转换为布尔值。

(程序员的软技能:ke.qq.com/course/6034346)

2. 显式转换。
虽然JavaScript会自动执行许多类型的转换,但在某些情况下,显式转换是必要的,或者出于代码清晰性的考虑而优先选择。
执行显式类型转换的最简单方法是使用Boolean()Number()String()函数:

// => 3
Number("3")
// => "false":或使用`false.toString()`
String(false)
// => true
Boolean([])

除了nullundefined之外的所有值都有一个toString()方法,这个方法的结果通常与String()函数的结果相同。

顺便说一下,Boolean()Number()String()函数也可以作为构造函数与new关键字一起使用。然而,这会创建与原始布尔值、数字和字符串值类似的“包装”对象,这是JavaScript早期版本中的历史遗留问题,现在不再需要。

一些JavaScript运算符执行隐式类型转换,这可以用于类型转换的目的。如果+运算符的一个操作数是字符串,它会将另一个操作数转换为字符串。一元+运算符将其操作数转换为数字,一元!运算符将其操作数转换为布尔值,然后取反。这些事实导致了常见的类型转换模式,例如:

// => String(x)
x + ""
// => Number(x)
+x
// => Number(x)
x - 0
// => Boolean(x):注意双重否定
!!x

格式化和解析数字是计算机程序中常见的错误来源,JavaScript提供了专门的函数和方法,以便更精确地控制从数字到字符串的转换,反之亦然。Number类定义了toString()方法,它接受一个可选的参数来指定基数或底数。如果未提供此参数, 默认基数为10。但是,也可以将数字转换为其他基数(2到36)。例如:

let n = 17;
// 二进制 == "0b10001"
let binary = "0b" + n.toString(2);
// 八进制 == "0o21"
let octal = "0o" + n.toString(8);
// 十六进制 == "0x11"
let hex = "0x" + n.toString(16);

在处理金融或科学数据时,您可能需要控制结果字符串中的小数位数或有效数字,或者可能想使用指数表示法。Number类提供了从数字到字符串的转换的三种方法。toFixed()将数字转换为字符串,并允许您指定小数点后的位数。该方法不使用指数表示法。toExponential()使用指数表示法将数字转换为字符串,其中小数点前有一位数字,小数点后有指定数量的位数(比您指定的位数多一个有效数字)。toPrecision()将数字转换为具有指定数量有效数字的字符串。如果指定的有效数字不足以表示数字的整数部分,则使用指数表示法。请注意,这三种方法会根据需要进行四舍五入或填充零。以下是一些示例:

let n = 123456.789;
// => "123457"
n.toFixed(0)
// => "123456.79"
n.toFixed(2)
// => "123456.78900"
n.toFixed(5)
// => "1.2e+5"
n.toExponential(1)
// => "1.235e+5"
n.toExponential(3)
// => "1.235e+5"
n.toPrecision(4)
// => "123456.8"
n.toPrecision(7)
// => "123456.7890"
n.toPrecision(18)

如果将字符串传递给Number()转换函数,它将尝试将字符串解析为整数或浮点数文字。此函数仅适用于基数为10的整数,并且不允许尾部非数字字符。parseInt()parseFloat()函数(都不是任何类的方法,而是全局函数)更具灵活性。parseInt()仅解析整数,而parseFloat()解析整数和浮点数。如果字符串以"0x"或"0X"开头,parseInt()将其解析为十六进制数。parseInt()parseFloat()都会跳过前导空格,解析尽可能多的数字字符,并忽略后跟的任何非数字字符。如果第一个非空格字符不是有效的数字文字,则它们返回NaN

// => 3
parseInt("3 blind mice")
// => 3.14
parseFloat(" 3.14 meters")
// => -12
parseInt("-12.34")
// => 255
parseInt("0xFF")
// => 255
parseInt("0xff")
// => -255
parseInt("-0xFF")
// => 0.1
parseFloat(".1")
// => 0
parseInt("0.1")
// => NaN:整数不能以"."
parseInt(".1")
// => NaN:数字文字不能以"$"
parseFloat("$72.47")

parseInt()函数接受一个可选的第二个参数,用于指定解析数字的基数(底数)。基数的有效值为2到36。例如:

// => 3:(1 * 2 + 1)
parseInt("11", 2)
// => 255:(15 * 16 + 15)
parseInt("ff", 16)
// => 1295:(35 * 36 + 35)
parseInt("zz", 36)
// => 63:(7 * 8 + 7)
parseInt("077", 8)
// => 77:(7 * 10 + 7)
parseInt("077", 10)

(程序员的软技能:ke.qq.com/course/6034346)

3. 对象到原始值的转换。

接下来,我们将讨论JavaScript在将对象转换为原始值时遵循的复杂规则。这些规则既冗长又有些复杂。

将JavaScript对象转换为原始值的复杂性主要源于一些对象类型具有多个表示作为原始值的方式。例如,Date对象可以表示为字符串或时间戳。JavaScript为将对象转换为原始值指定了三个基本算法。

(1) 字符串提示。该算法返回一个原始值,并在可能的情况下返回一个字符串。
(2) 数字提示。该算法返回一个原始值,并在可能的情况下返回一个数字。
(3) 默认提示。该算法不偏好特定的原始值类型,并由类定义以指定其自己的转换规则。除Date类外,所有内置的JavaScript类型都实现了数字提示算法,而Date类实现了字符串提示算法。

让我们深入探讨这些算法在JavaScript中的使用。

从对象到布尔值的转换。
将对象转换为布尔值很简单:所有对象都被转换为true。请注意,这种转换不需要前面提到的对象到原始值转换算法;它直接适用于所有对象。这包括空数组,甚至包装对象如new Boolean(false)

从对象到字符串的转换。
当将对象转换为字符串时,JavaScript首先使用字符串提示算法将其转换为原始值,然后将得到的原始值转换为字符串。此转换在将对象传递给接受字符串参数的内置函数时发生,例如使用String()作为转换函数或将对象插入到模板文字中时。

从对象到数字的转换。
当需要将对象转换为数字时,JavaScript首先使用数字提示算法将其转换为原始值,然后将得到的原始值转换为数字。接受数字参数的内置JavaScript函数和方法执行此转换,当操作数是对象时,JavaScript的大多数运算符(除了下面指出的几个例外)也会执行此转换。

运算符转换的特殊情况。
在这里,我们只会讨论不遵循上述基本对象到字符串或对象到数字转换规则的运算符异常。

首先,JavaScript中的+运算符既执行数字加法又执行字符串连接。如果一个操作数是对象,JavaScript将使用默认提示算法将对象转换为原始值。如果两个操作数都是原始值,则检查它们的类型。如果至少有一个参数是字符串,则另一个原始值也会被转换为字符串,然后将两个字符串连接在一起。否则,两个参数都会被转换为数字,然后计算它们的和。

其次,==!=运算符执行松散的等式测试,允许类型转换。如果一个操作数是对象,另一个操作数是原始值,这些运算符将使用默认提示算法将对象转换为原始值,然后比较两个原始值。

最后,比较运算符<=>>=将操作数比较为数字或字符串。如果一个操作数是对象,则使用默认提示算法将对象转换为原始值,使用数字提示算法。然而,需要注意的是,与对象到数字的转换不同,从这种对象到数字的转换的结果原始值不会进一步转换为数字。

值得注意的是,虽然Date对象的数值表示可以通过<>进行有意义的比较,但其字符串表示则不能。JavaScript中的<=>>=运算符使用数字提示算法的事实意味着我们可以有意义地比较两个Date对象的顺序。

toString()valueOf()方法。
所有对象都继承了两个在对象到原始值转换中使用的方法。在解释to-string、to-number和默认转换算法之前,我们必须首先介绍这两个方法。

第一个方法是toString(),其任务是返回对象的字符串表示。默认情况下,toString()方法不返回任何特定的内容:

// => "[object Object]"
({x: 1, y: 2}).toString()

许多类定义了它们自己特定版本的toString()方法。例如,Array类的toString()方法将数组的每个元素转换为字符串,并使用逗号作为分隔符连接它们。Function类的toString()方法将用户定义的函数转换为包含函数的JavaScript源代码的字符串。Date类的toString()方法返回一个可读(并可被JavaScript解析)的日期和时间字符串。RegExp类的toString()方法将RegExp对象转换为类似于RegExp字面量的字符串:

// => "1,2,3"
[1, 2, 3].toString()
// => "function(x) { f(x); }"
(function(x) { f(x); }).toString()
// => "/\\d+/g"
/\d+/g.toString()
// => "Wed Jan 01 2030 00:00:00 GMT-0800 (Pacific Standard Time)"
let d = new Date(2030, 0, 1);
d.toString()

另一个转换函数称为valueOf()。该方法的任务没有明确定义,并且通常被认为返回表示对象的原始值(如果存在这样的值)。由于对象是复杂的值,大多数对象不能由单个原始值准确表示,因此valueOf()方法默认情况下只返回对象本身,而不是原始值。String、Number和Boolean等封装类的valueOf()方法也只返回封装的原始值。Array、Function和RegExp等类继承默认的valueOf()方法,该方法返回对象本身。在这些类型的实例上调用valueOf()方法将返回对象本身。Date类的valueOf()方法返回自1970年1月1日以来的毫秒数,表示日期的内部表示:

// 2030年1月1日(太平洋时间)
let d = new Date(2030, 0, 1);
// => 1893427200000
d.valueOf()

对象到原始值转换算法。
现在我们已经介绍了toString()valueOf()方法,我们可以粗略地解释前面提到的三个对象到原始值转换算法的实现。

· 字符串提示算法首先尝试toString()方法。如果此方法被定义并返回一个原始值,JavaScript将使用该值(即使它不是字符串)。如果toString()不存在或存在但返回一个对象,JavaScript会尝试valueOf()方法。如果此方法存在并返回一个原始值,JavaScript将使用该值。否则,转换失败并抛出TypeError

· 数字提示算法类似于字符串提示算法,只是它首先尝试valueOf()方法,然后是toString()方法。

· 默认提示算法取决于要转换的对象的类。如果是Date对象,JavaScript将使用字符串提示算法。对于其他对象类型,JavaScript将使用数字提示算法。

这些规则适用于所有内置的JavaScript类型,并作为所有自定义类的默认规则。

在结束类型转换的讨论之前,重要的是要注意,从原始值转换规则的细节可以解释为什么空数组会转换为数字0,以及为什么只有一个元素的数组也可以转换为数字:

// => 0:有点令人惊讶!
Number([])
// => 99:真的吗?
Number([99])

从对象到数字的转换首先使用数字提示算法将对象转换为原始值,然后将得到的原始值转换为数字。数字提示算法首先尝试valueOf(),如果不可用,则回退到toString()。Array类继承了默认的valueOf()方法,它不返回原始值。因此,当将数组转换为数字时,它最终调用toString()方法。空数组转换为空字符串,空字符串转换为数字0。只有一个元素的数组会转换为其元素的字符串表示,如果该元素是数字,则会将其再次转换回数字。

(程序员的软技能:ke.qq.com/course/6034346)