by Alexey Samoshkin
通过阿列克谢·萨莫什金(Alexey Samoshkin)
JavaScript类型强制解释 (JavaScript type coercion explained)
了解你的引擎 (Know your engines)
[Edit 2/5/2018]: This post is now available in Russian. Claps to Serj Bulavyk for his efforts.
[Edit 2/5/2018] :该帖子现在可用俄语 。 为Serj Bulavyk的努力鼓掌 。
Type coercion is the process of converting value from one type to another (such as string to number, object to boolean, and so on). Any type, be it primitive or an object, is a valid subject for type coercion. To recall, primitives are: number, string, boolean, null, undefined + Symbol (added in ES6).
类型强制是将值从一种类型转换为另一种类型(例如,字符串到数字,对象到布尔值等)的过程。 任何类型,无论是原始类型还是对象,都是强制类型的有效主体。 回想一下,原语是:数字,字符串,布尔值,空值,未定义+符号(在ES6中添加)。
As an example of type coercion in practice, look at the JavaScript Comparison Table, which shows how the loose equality ==
operator behaves for different a
and b
types. This matrix looks scary due to implicit type coercion that ==
operator does, and it’s hardly possible to remember all those combinations. And you don’t have to do that — just learn the underlying type coercion principles.
作为实践中类型强制的示例,请查看JavaScript比较表 ,该表显示了松散相等==
运算符对于不同的a
和b
类型的行为。 由于==
运算符具有隐式类型强制,因此该矩阵看起来很吓人,几乎不可能记住所有这些组合。 而且您不必这样做-只需学习底层的类型强制性原则。
This article goes in-depth on how type coercion works in JavaScript, and will arm you with the essential knowledge, so you can feel confident explaining what following expressions calculate to. By the end of the article I’ll show answers and explain them.
本文深入探讨了类型强制在JavaScript中的工作方式,并为您提供了必不可少的知识,因此您可以自信地解释以下表达式的计算结果。 在文章末尾,我将显示答案并进行解释。
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0
Yes, this list is full of pretty silly things you can do as a developer. In 90% of use cases it’s better to avoid implicit type coercion. Consider this list as a learning exercise to test your knowledge on how type coercion works. If you’re bored, you can find more examples on wtfjs.com.
是的,此列表充满了您作为开发人员可以做的相当愚蠢的事情。 在90%的用例中,最好避免隐式类型强制。 将此清单视为学习练习,以测试您对类型强制如何工作的了解。 如果无聊,可以在wtfjs.com上找到更多示例。
By the way, sometimes you might face such questions on the interview for a JavaScript developer position. So, keep reading ?
顺便说一句,有时您可能会在面试中遇到这样的问题,以获取JavaScript开发人员的职位。 所以,继续阅读?
内隐与外显强迫 (Implicit vs. explicit coercion)
Type coercion can be explicit and implicit.
类型强制可以是显式的也可以是隐式的。
When a developer expresses the intention to convert between types by writing the appropriate code, like Number(value)
, it’s called explicit type coercion (or type casting).
当开发人员通过编写适当的代码(例如Number(value)
表示要在类型之间进行转换的意图时,这称为显式类型强制 (或类型转换)。
Since JavaScript is a weakly-typed language, values can also be converted between different types automatically, and it is called implicit type coercion. It usually happens when you apply operators to values of different types, like1 == null
, 2/’5'
, null + new Date()
, or it can be triggered by the surrounding context, like with if (value) {…}
, where value
is coerced to boolean.
由于JavaScript是一种弱类型的语言,因此值也可以自动在不同类型之间转换,这被称为隐式类型强制 。 通常在将运算符应用于不同类型的值(例如1 == null
, 2/'5'
, null + new Date()
,或者它可以由周围的上下文触发,例如if (value) {…}
,其中value
被强制为布尔值。
One operator that does not trigger implicit type coercion is ===
, which is called the strict equality operator. The loose equality operator ==
on the other hand does both comparison and type coercion if needed.
一个不触发隐式类型强制的运算符是===
,称为严格相等运算符。 另一方面,如果需要,宽松的相等运算符==
会同时执行比较和类型强制。
Implicit type coercion is a double edge sword: it’s a great source of frustration and defects, but also a useful mechanism that allows us to write less code without losing the readability.
隐式类型强制是一把双刃剑:它是挫折和缺陷的重要来源,而且是一种有用的机制,它使我们可以编写更少的代码而不会损失可读性。
三种转换 (Three types of conversion)
The first rule to know is there are only three types of conversion in JavaScript:
要知道的第一个规则是JavaScript中只有三种转换类型:
- to string 串
- to boolean 布尔值
- to number 编号
Secondly, conversion logic for primitives and objects works differently, but both primitives and objects can only be converted in those three ways.
其次,基元和对象的转换逻辑工作方式不同,但是基元和对象只能以这三种方式进行转换。
Let’s start with primitives first.
让我们先从原始体开始。
字符串转换 (String conversion)
To explicitly convert values to a string apply the String()
function. Implicit coercion is triggered by the binary +
operator, when any operand is a string:
要将值显式转换为字符串,请使用String()
函数。 当任何操作数为字符串时,隐式强制由二进制+
运算符触发:
String(123) // explicit
123 + '' // implicit
All primitive values are converted to strings naturally as you might expect:
如您所料,所有原始值都会自然地转换为字符串:
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
Symbol conversion is a bit tricky, because it can only be converted explicitly, but not implicitly. Read more on Symbol
coercion rules.
符号转换有点棘手,因为它只能显式转换,而不能隐式转换。 阅读更多有关Symbol
强制规则的信息。
String(Symbol('my symbol')) // 'Symbol(my symbol)'
'' + Symbol('my symbol') // TypeError is thrown
布尔转换 (Boolean conversion)
To explicitly convert a value to a boolean apply the Boolean()
function.Implicit conversion happens in logical context, or is triggered by logical operators ( ||
&&
!
) .
若要将值显式转换为布尔值,请使用Boolean()
函数。隐式转换发生在逻辑上下文中,或由逻辑运算符( ||
&&
!
)触发。
Boolean(2) // explicit
if (2) { ... } // implicit due to logical context
!!2 // implicit due to logical operator
2 || 'hello' // implicit due to logical operator
Note: Logical operators such as ||
and &&
do boolean conversions internally, but actually return the value of original operands, even if they are not boolean.
注意 :逻辑运算符,例如||
和&&
在内部进行布尔转换,但实际上返回原始操作数的值 ,即使它们不是布尔值也是如此。
// returns number 123, instead of returning true
// 'hello' and 123 are still coerced to boolean internally to calculate the expression
let x = 'hello' && 123; // x === 123
As soon as there are only 2 possible results of boolean conversion: true
or false
, it’s just easier to remember the list of falsy values.
布尔转换只有两种可能的结果: true
或false
,更容易记住虚假值列表。
Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
Any value that is not in the list is converted to true
, including object, function, Array
, Date
, user-defined type, and so on. Symbols are truthy values. Empty object and arrays are truthy values as well:
不在列表中的任何值都将转换为true
,包括object,function, Array
, Date
,用户定义的类型等。 符号是真实的价值。 空对象和数组也是真实值:
Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
数值转换 (Numeric conversion)
For an explicit conversion just apply the Number()
function, same as you did with Boolean()
and String()
.
对于显式转换,只需应用Number()
函数,就像使用Boolean()
和String()
。
Implicit conversion is tricky, because it’s triggered in more cases:
隐式转换是棘手的,因为它在更多情况下被触发:
comparison operators (
>
,<
,<=
,>=
)比较运算符(
>
,<
,<=
,>=
)bitwise operators (
|
&
^
~
)按位运算符(
|
&
^
~
)arithmetic operators (
-
+
*
/
%
). Note, that binary+
does not trigger numeric conversion, when any operand is a string.算术运算符(
-
+
*
/
%
)。 注意,当任何操作数是字符串时,二进制+
不会触发数字转换。unary
+
operator一元
+
运算符loose equality operator
==
(incl.!=
).松散相等运算符
==
(包括!=
)。Note that
注意
==
does not trigger numeric conversion when both operands are strings.当两个操作数均为字符串时,
==
不会触发数字转换。
Number('123') // explicit
+'123' // implicit
123 != '456' // implicit
4 > '5' // implicit
5/null // implicit
true | 0 // implicit
Here is how primitive values are converted to numbers:
以下是将原始值转换为数字的方式:
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12.34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
When converting a string to a number, the engine first trims leading and trailing whitespace, \n
, \t
characters, returning NaN
if the trimmed string does not represent a valid number. If string is empty, it returns 0
.
将字符串转换为数字时,引擎会首先修剪前导和尾随空格\n
, \t
字符,如果修剪后的字符串不代表有效数字,则返回NaN
。 如果string为空,则返回0
。
null
and undefined
are handled differently: null
becomes 0
, whereas undefined
becomes NaN
.
null
和undefined
的处理方式不同: null
变为0
,而undefined
变为NaN
。
Symbols cannot be converted to a number neither explicitly nor implicitly. Moreover, TypeError
is thrown, instead of silently converting to NaN
, like it happens for undefined
. See more on Symbol conversion rules on MDN.
符号不能显式或隐式地转换为数字。 而且, TypeError
,而不是像undefined
那样静默转换为NaN
。 有关MDN上的符号转换规则的更多信息,请参见。
Number(Symbol('my symbol')) // TypeError is thrown
+Symbol('123') // TypeError is thrown
There are two special rules to remember:
要记住两个特殊规则 :
When applying
==
tonull
orundefined
, numeric conversion does not happen.null
equals only tonull
orundefined
, and does not equal to anything else.当将
==
应用于null
或undefined
,不会发生数字转换。null
仅等于null
或undefined
,不等于其他任何值。
null == 0 // false, null is not converted to 0
null == null // true
undefined == undefined // true
null == undefined // true
2. NaN does not equal to anything even itself:
2. NaN甚至不等于任何东西:
if (value !== value) { console.log("we're dealing with NaN here") }
输入对象的强制 (Type coercion for objects)
So far, we’ve looked at type coercion for primitive values. That’s not very exciting.
到目前为止,我们已经针对原始值研究了类型强制。 那不是很令人兴奋。
When it comes to objects and engine encounters expression like [1] + [2,3]
, first it needs to convert an object to a primitive value, which is then converted to the final type. And still there are only three types of conversion: numeric, string and boolean.
对于对象,引擎遇到诸如[1] + [2,3]
类的表达式时,首先需要将对象转换为原始值,然后将其转换为最终类型。 仍然只有三种类型的转换:数字,字符串和布尔值。
The simplest case is boolean conversion: any non-primitive value is always coerced to true
, no matter if an object or an array is empty or not.
最简单的情况是布尔转换:任何非原始值总是强制为true
,无论对象或数组是否为空。
Objects are converted to primitives via the internal [[ToPrimitive]]
method, which is responsible for both numeric and string conversion.
通过内部[[ToPrimitive]]
方法将对象转换为基元,该方法负责数字和字符串转换。
Here is a pseudo implementation of [[ToPrimitive]]
method:
这是[[ToPrimitive]]
方法的伪实现:
[[ToPrimitive]]
is passed with an input value and preferred type of conversion: Number
or String
. preferredType
is optional.
[[ToPrimitive]]
与输入值和首选的转换类型一起传递: Number
或String
。 preferredType
是可选的。
Both numeric and string conversion make use of two methods of the input object: valueOf
and toString
. Both methods are declared on Object.prototype
and thus available for any derived types, such as Date
, Array
, etc.
数字转换和字符串转换都使用输入对象的两种方法: valueOf
和toString
。 两种方法都在Object.prototype
上Object.prototype
,因此可用于任何派生类型,例如Date
, Array
等。
In general the algorithm is as follows:
通常,算法如下:
- If input is already a primitive, do nothing and return it. 如果输入已经是原始类型,则不执行任何操作并返回它。
2. Call input.toString()
, if the result is primitive, return it.
2.调用input.toString()
,如果结果是原始的,则将其返回。
3. Call input.valueOf()
, if the result is primitive, return it.
3.调用input.valueOf()
,如果结果是原始的,则将其返回。
4. If neither input.toString()
nor input.valueOf()
yields primitive, throw TypeError
.
4.如果既不input.toString()
也不input.valueOf()
产生原始的,扔TypeError
。
Numeric conversion first calls valueOf
(3) with a fallback to toString
(2). String conversion does the opposite: toString
(2) followed by valueOf
(3).
数值转换首先调用valueOf
(3),并回toString
(2)。 字符串转换执行相反的操作: toString
(2),后跟valueOf
(3)。
Most built-in types do not have valueOf
, or have valueOf
returning this
object itself, so it’s ignored because it’s not a primitive. That’s why numeric and string conversion might work the same — both end up calling toString()
.
大多数内置类型不具有valueOf
,也没有valueOf
返回this
对象本身,因此它被忽略,因为它不是原始类型。 这就是为什么数字和字符串转换可能工作相同的原因-两者都最终调用toString()
。
Different operators can trigger either numeric or string conversion with a help of preferredType
parameter. But there are two exceptions: loose equality ==
and binary +
operators trigger default conversion modes (preferredType
is not specified, or equals to default
). In this case, most built-in types assume numeric conversion as a default, except Date
that does string conversion.
不同的运算符可以在preferredType
参数的帮助下触发数字或字符串转换。 但是有两个例外:松散相等==
和二进制+
运算符触发默认转换模式(未指定preferredType
或等于default
)。 在这种情况下,大多数内置类型都将数字转换作为默认值,但Date
会进行字符串转换。
Here is an example of Date
conversion behavior:
这是Date
转换行为的示例:
You can override the default toString()
and valueOf()
methods to hook into object-to-primitive conversion logic.
您可以覆盖默认的toString()
和valueOf()
方法,以挂接到对象到原始的转换逻辑。
Notice how obj + ‘’
returns ‘101’
as a string. +
operator triggers a default conversion mode, and as said before Object
assumes numeric conversion as a default, thus using the valueOf()
method first instead of toString()
.
注意obj + ''
如何以字符串形式返回'101'
。 +
运算符会触发默认的转换模式,如之前所说, Object
假定数字转换为默认转换,因此首先使用valueOf()
方法而不是toString()
。
ES6 Symbol.toPrimitive方法 (ES6 Symbol.toPrimitive method)
In ES5 you can hook into object-to-primitive conversion logic by overriding toString
and valueOf
methods.
在ES5中,您可以通过重写toString
和valueOf
方法来陷入对象到原始的转换逻辑。
In ES6 you can go farther and completely replace internal[[ToPrimitive]]
routine by implementing the[Symbol.toPrimtive]
method on an object.
在ES6中,您可以更进一步,通过在对象上实现[Symbol.toPrimtive]
方法来完全替换内部[[ToPrimitive]]
例程。
例子 (Examples)
Armed with the theory, now let’s get back to our examples:
有了理论,现在让我们回到例子:
true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1] > null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!!"false" == !!"true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
[1,2,3] == [1,2,3] // false
{}+[]+{}+[1] // '0[object Object]1'
!+[]+[]+![] // 'truefalse'
new Date(0) - 0 // 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0'
Below you can find explanation for each the expression.
您可以在下面找到每个表达式的说明。
Binary +
operator triggers numeric conversion for true
and false
二进制+
运算符触发数字转换为true
和false
true + false
==> 1 + 0
==> 1
Arithmetic division operator /
triggers numeric conversion for string '6'
:
算术除法运算符/
触发字符串'6'
数字转换:
12 / '6'
==> 12 / 6
==>> 2
Operator +
has left-to-right associativity, so expression "number" + 15
runs first. Since one operand is a string, +
operator triggers string conversion for the number 15
. On the second step expression "number15" + 3
is evaluated similarly.
运算符+
具有从左到右的关联性,因此表达式"number" + 15
首先运行。 由于一个操作数是字符串,所以+
运算符将触发数字15
字符串转换。 在第二步中,表达式"number15" + 3
的计算方式类似。
“number” + 15 + 3
==> "number15" + 3
==> "number153"
Expression 15 + 3
is evaluated first. No need for coercion at all, since both operands are numbers. On the second step, expression 18 + 'number'
is evaluated, and since one operand is a string, it triggers a string conversion.
首先评估表达式15 + 3
。 因为两个操作数都是数字,所以根本不需要强制。 在第二步中,对表达式18 + 'number'
求值,并且由于一个操作数是字符串,因此它将触发字符串转换。
15 + 3 + "number"
==> 18 + "number"
==> "18number"
Comparison operator &
gt; triggers numeric conversion for
[1] and n
ull .
比较运算符&
GT; 触发数字转换f or
[1] nd n
null。
[1] > null
==> '1' > 0
==> 1 > 0
==> true
Unary +
operator has higher precedence over binary +
operator. So +'bar'
expression evaluates first. Unary plus triggers numeric conversion for string 'bar'
. Since the string does not represent a valid number, the result is NaN
. On the second step, expression 'foo' + NaN
is evaluated.
一元+
运算符的优先级高于二元+
运算符。 因此, +'bar'
表达式首先计算。 一元加号触发字符串'bar'
数字转换。 由于该字符串不代表有效数字,因此结果为NaN
。 第二步,计算表达式'foo' + NaN
。
"foo" + + "bar"
==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
==
operator triggers numeric conversion, string 'true'
is converted to NaN, boolean true
is converted to 1.
==
运算符触发数字转换,字符串'true'
转换为NaN,布尔值true
转换为1。
'true' == true
==> NaN == 1
==> false
false == 'false'
==> 0 == NaN
==> false
==
usually triggers numeric conversion, but it’s not the case with null
. null
equals to null
or undefined
only, and does not equal to anything else.
==
通常会触发数字转换,但null
并非如此。 null
等于null
或仅undefined
,不等于其他任何值。
null == ''
==> false
!!
operator converts both 'true'
and 'false'
strings to boolean true
, since they are non-empty strings. Then, ==
just checks equality of two boolean true's
without any coercion.
!!
运算符会将'true'
和'false'
字符串都转换为boolean true
,因为它们是非空字符串。 然后, ==
仅检查两个布尔true's
相等性而没有任何强制性。
!!"false" == !!"true"
==> true == true
==> true
==
operator triggers a numeric conversion for an array. Array’s valueOf()
method returns the array itself, and is ignored because it’s not a primitive. Array’s toString()
converts ['x']
to just 'x'
string.
==
运算符触发数组的数字转换。 Array的valueOf()
方法返回数组本身,由于它不是基元而被忽略。 数组的toString()
将['x']
转换为仅'x'
字符串。
['x'] == 'x'
==> 'x' == 'x'
==> true
+
operator triggers numeric conversion for []
. Array’s valueOf()
method is ignored, because it returns array itself, which is non-primitive. Array’s toString
returns an empty string.
+
运算符触发[]
数字转换。 数组的valueOf()
方法将被忽略,因为它返回的数组本身是非原始的。 数组的toString
返回一个空字符串。
On the the second step expression '' + null + 1
is evaluated.
在第二步中,表达式'' + null + 1
被求值。
[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'
Logical ||
and &&
operators coerce operands to boolean, but return original operands (not booleans). 0
is falsy, whereas '0'
is truthy, because it’s a non-empty string. {}
empty object is truthy as well.
逻辑||
和&&
运算符将操作数强制转换为布尔值,但返回原始操作数(不是布尔值)。 0
是虚假的,而'0'
是真实的,因为它是一个非空字符串。 {}
空对象也是真实的。
0 || "0" && {}
==> (0 || "0") && {}
==> (false || true) && true // internally
==> "0" && {}
==> true && true // internally
==> {}
No coercion is needed because both operands have same type. Since ==
checks for object identity (and not for object equality) and the two arrays are two different instances, the result is false
.
不需要强制,因为两个操作数具有相同的类型。 由于==
检查对象身份(而不检查对象是否相等),并且两个数组是两个不同的实例,因此结果为false
。
[1,2,3] == [1,2,3]
==> false
All operands are non-primitive values, so +
starts with the leftmost triggering numeric conversion. Both Object’s
and Array’s
valueOf
method returns the object itself, so it’s ignored. toString()
is used as a fallback. The trick here is that first {}
is not considered as an object literal, but rather as a block declaration statement, so it’s ignored. Evaluation starts with next +[]
expression, which is converted to an empty string via toString()
method and then to 0
.
所有操作数都是非原始值,因此+
从最左边的触发数字转换开始。 Object's
和Array's
valueOf
方法都返回对象本身,因此将其忽略。 toString()
用作后备。 这里的技巧是,第一个{}
不被视为对象文字,而是块声明语句,因此将其忽略。 评估从下一个+[]
表达式开始,该表达式通过toString()
方法转换为空字符串,然后转换为0
。
{}+[]+{}+[1]
==> +[]+{}+[1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'
This one is better explained step by step according to operator precedence.
可以根据操作员的优先顺序逐步说明这一点。
!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
-
operator triggers numeric conversion for Date
. Date.valueOf()
returns number of milliseconds since Unix epoch.
-
运算符触发Date
数字转换。 Date.valueOf()
返回自Unix时代以来的毫秒数。
new Date(0) - 0
==> 0 - 0
==> 0
+
operator triggers default conversion. Date assumes string conversion as a default one, so toString()
method is used, rather than valueOf()
.
+
运算符触发默认转换。 Date假定字符串转换为默认值,所以使用toString()
方法,而不是valueOf()
。
new Date(0) + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
翻译自: https://www.freecodecamp.org/news/js-type-coercion-explained-27ba3d9a2839/