这篇文章是 JavaScript 怪异系列的一部分
JavaScript 是对值的接受是非常宽松的。例如在任何期待一个 number 类型的值的地方,它不会拒绝来自其他类型的值,而是会试图转换它们:
> '5' - '2'
3
> '5' * '2'
10
自动转换成 boolean 值是很少出问题并且很有用。这里先埋下个伏笔(我们将用它解决一些怪异问题)。然而,自动转换为 string 可能会造成一些问题
不管何时 JavaScript 接受一个 boolean 值时(例如,if 语句的条件),任何值都可以用。它要么解释为 true,要么解释为 false。下面的值都会被解释为 false:
undefined, null
Boolean: false
Number: -0, +0, NaN
String: ''
其他所有的值都会被当作 true。值被解释为 false 的称作 falsy(假值),值被解释为 true 的称作 truthy(真值)。你可以使用 Boolean 函数来测试一个值会被解释为什么。它会把它的参数转换为 boolean 值:
> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
在 web 开发中,你会经常获取到实际上是 number 或 boolean 而当作 string 类型的值。例如,当用户在表单输入一些数据时。如果你忘记了显式转换这些 string 的话,JavaScript 会给你带来两个消极方面的惊喜:首先,不会有任何警告。其次,值会被自动转换,但是是错的。例如,操作符加号(+)就有问题,因为只要操作符的一边是 string 类型的,它就会当成字符串连接符。在下面的例子中,假设我们执行的是 1 和 5 相加,而实际上,我们执行的是 ‘1’ 和 ‘5’ 字符串连接
> var x = '5'; // 错误假设:x 是 number 类型
> x + 1
'51'
此外,还有一些极少数的 falsy 值,如果被转换为 string 类型的,就会被当成 truthy,例如:false
> Boolean(false) // truthy?
false
> String(false)
'false'
> Boolean('false') // truthy?
true
例如:undefined
> Boolean(undefined) // truthy?
false
> String(undefined)
'undefined'
> Boolean('undefined') // truthy?
true
如果 JavaScript 期待的是 number 或 string 类型的值的时候,object 才会发生隐式转换。第一种情况下(期待 number),会按照三个步骤进行转换:
调用 valueOf() 方法,如果结果是原始类型(非 object),那么继续调用 ToNumber否则,调用 toString() 方法。如果结果是原始类型,那么继续那么继续调用 ToNumber否则,抛出一个 TypeError 错误
译者改:
- 调用 ToPrimitive, 如果是原始类型,直接返回
- 调用 valueOf() 方法,如果结果是原始类型,那么继续调用 ToNumber
- 否则,调用 toString() 方法。如果结果是原始类型,那么继续调用 ToNumber
- 否则,抛出一个 TypeError 错误
译者注:这里注意 Date 对象(object)特殊,valueOf 和 toString 调用顺序不同
第 1 步的例子:
> 3 * { valueOf: function () { return 5 } }
15
第 3 步的例子:
> function returnObject() { return {} }
> 3 * { valueOf: returnObject, toString: returnObject }
TypeError: 无法将 object 转换为原始类型
如果是 JavaScript 转换为 string 类型,那步骤 1 和步骤 2 进行交换:首先试图调用 toString() 方法,然后调用 valueOf() 方法
最好的办法是在使用它们之前就显式转换为期望的类型。一个迷你的解决方案是用 Boolean(),Number() 和 String() 方法:
function handleFormData(formData) {
var givenName = String(formData.givenName);
var age = Number(formData.age);
...
}
这些函数总会返回一个值(它们永远都不会抛出异常)。然而 Number() 函数在它不能转换一个值时会返回 NaN
[1]:
> Number('xyz')
NaN
> Number(undefined)
NaN
> Number(null)
0
> Number(true)
1
更详细的解决方案是在你转换这些值时,先检查下它们的格式(例如,人们不能把 ‘xyz’ 作为它们的年龄),如果不对,要采取适当的解决措施
感谢@hsy0 的评论,原文作者这里没有讲清楚,毕竟一千个人一千个哈姆雷特,我们还是直接看标准吧 ecma-262/6.0/#sec-toprimitive
其实真实转换过程如下(作者在另一篇文章中提到了,😂,和标准一致),就不翻译了
An object obj is converted to a number by calling ToPrimitive(obj, Number) and then applying ToNumber() to the (primitive) result.
An object obj is converted to a number by calling ToPrimitive(obj, String) and then applying ToString() to the (primitive) result.