TLDR
La coerción de tipos, o conversión de tipos implícita, permite una escritura débil y se usa en todo JavaScript. La mayoría de los operadores (con la notable excepción de los operadores de igualdad estricta ===
y !==
), y las operaciones de verificación de valor (por ejemplo if(value)...
), forzarán los valores que se les proporcionen, si los tipos de esos valores no son inmediatamente compatibles con la operación.
El mecanismo preciso utilizado para coaccionar un valor depende de la expresión que se evalúa. En la pregunta, se está utilizando el operador de suma .
El operador de suma primero se asegurará de que ambos operandos sean primitivos, lo que, en este caso, implica llamar al valueOf
método. El toString
método no se llama en esta instancia porque el valueOf
método reemplazado en el objeto x
devuelve un valor primitivo.
Entonces, debido a que uno de los operandos en la pregunta es una cadena, ambos operandos se convierten en cadenas. Este proceso utiliza la operación interna abstracta ToString
(nota: en mayúscula) y es distinto del toString
método en el objeto (o su cadena prototipo).
Finalmente, las cadenas resultantes se concatenan.
Detalles
En el prototipo de cada objeto de función de constructor correspondiente a cada tipo de lenguaje en JavaScript (es decir, Número, BigInt, Cadena, Booleano, Símbolo y Objeto), hay dos métodos: valueOf
y toString
.
El propósito de valueOf
es recuperar el valor primitivo asociado con un objeto (si tiene uno). Si un objeto no tiene un valor primitivo subyacente, simplemente se devuelve el objeto.
Si valueOf
se invoca contra una primitiva, la primitiva se encuadra automáticamente de la forma normal y se devuelve el valor de la primitiva subyacente. Tenga en cuenta que para las cadenas, el valor primitivo subyacente (es decir, el valor devuelto por valueOf
) es la propia representación de la cadena.
El siguiente código muestra que el valueOf
método devuelve el valor primitivo subyacente de un objeto contenedor, y muestra cómo las instancias de objetos no modificados que no corresponden a primitivas, no tienen ningún valor primitivo para devolver, por lo que simplemente regresan a sí mismas.
console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)
El propósito de toString
, por otro lado, es devolver una representación de cadena de un objeto.
Por ejemplo:
console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'
Para la mayoría de las operaciones, JavaScript intentará convertir silenciosamente uno o más operandos al tipo requerido. Este comportamiento se eligió para facilitar el uso de JavaScript. JavaScript inicialmente no tenía excepciones , y esto también puede haber jugado un papel en esta decisión de diseño. Este tipo de conversión de tipo implícita se denomina coerción de tipo y es la base del sistema de tipo flexible (débil) de JavaScript. Las complicadas reglas detrás de este comportamiento están destinadas a trasladar la complejidad del encasillamiento al lenguaje mismo y fuera de su código.
Durante el proceso coercitivo, hay dos modos de conversión que pueden ocurrir:
- Conversión de un objeto a primitivo (que podría implicar una conversión de tipo en sí), y
- La conversión directa a una instancia de tipo específico, el uso de un objeto de función constructor de uno de los tipos primitivos (es decir.
Number()
, Boolean()
, String()
Etc.)
Conversión a un primitivo
Cuando se intenta convertir tipos no primitivos en primitivos sobre los que operar, la operación abstracta ToPrimitive
se llama con una "sugerencia" opcional de 'número' o 'cadena'. Si se omite la sugerencia, la sugerencia predeterminada es 'número' (a menos que el @@toPrimitive
método haya sido anulado). Si la pista es 'cadena', entonces toString
se intenta primero, y valueOf
segundo si toString
no devolvió una primitiva. De lo contrario, viceversa. La sugerencia depende de la operación que solicita la conversión.
El operador de adición no proporciona ninguna pista, por lo que valueOf
se intenta primero. El operador de resta proporciona una pista de 'número', por lo que valueOf
se intenta primero. Las únicas situaciones que puedo encontrar en la especificación en las que la pista es 'cadena' son:
Object#toString
- La operación abstracta
ToPropertyKey
, que convierte un argumento en un valor que se puede utilizar como clave de propiedad.
Conversión de tipo directo
Cada operador tiene sus propias reglas para completar su operación. El operador de suma se utilizará primero ToPrimitive
para garantizar que cada operando sea una primitiva; luego, si cualquiera de los operandos es una cadena, entonces invocará deliberadamente la operación abstracta ToString
en cada operando, para entregar el comportamiento de concatenación de cadenas que esperamos con cadenas. Si, después del ToPrimitive
paso, ambos operandos no son cadenas, se realiza la suma aritmética.
A diferencia de la suma, el operador de resta no tiene un comportamiento sobrecargado, por lo que invocará toNumeric
en cada operando habiéndolos convertido primero en primitivas usando ToPrimitive
.
Entonces:
1 + 1 // 2
'1' + 1 // '11' Both already primitives, RHS converted to string, '1' + '1', '11'
1 + [2] // '12' [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
1 + {} // '1[object Object]' {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
2 - {} // NaN {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a' // NaN `ToPrimitive` passed 'number' hint), Number('a'), NaN
+'' // 0 `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1' // -1 `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{} // NaN `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
1 + 'a' // '1a' Both are primitives, one is a string, String(1) + 'a'
1 + {} // '1[object Object]' One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + [] // '' Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
1 - 'a' // NaN Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
1 - {} // NaN One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - [] // 0 Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0
Tenga en cuenta que el Date
objeto intrínseco es único, ya que es el único intrínseco que anula el @@toPrimitive
método predeterminado , en el que se presume que la sugerencia predeterminada es 'cadena' (en lugar de 'número'). La razón para tener esto es que las Date
instancias se traduzcan a cadenas legibles por defecto, en lugar de su valor numérico, para la conveniencia del programador. Puede anular @@toPrimitive
sus propios objetos usando Symbol.toPrimitive
.
La siguiente cuadrícula muestra los resultados de coerción para el operador de igualdad abstracto ( ==
) ( fuente ):

Vea también .
window.console.log (x);
oalert (x);
?alert(x)
muestrafoo
ywindow.console.log(x)
muestraObject { toString: x.toString(), valueOf: x.valueOf() }
.