TLDR
La coercition de type, ou conversion de type implicite, permet un typage faible et est utilisée dans tout JavaScript. La plupart des opérateurs (à l'exception notable des opérateurs d'égalité stricte ===et !==) et des opérations de vérification de valeur (par exemple if(value)...) contraindront les valeurs qui leur sont fournies, si les types de ces valeurs ne sont pas immédiatement compatibles avec l'opération.
Le mécanisme précis utilisé pour forcer une valeur dépend de l'expression évaluée. Dans la question, l' opérateur d'addition est utilisé.
L'opérateur d'addition s'assurera d'abord que les deux opérandes sont des primitives, ce qui, dans ce cas, implique l'appel de la valueOfméthode. La toStringméthode n'est pas appelée dans cette instance car la valueOfméthode surchargée sur l'objet xrenvoie une valeur primitive.
Ensuite, comme l'un des opérandes de la question est une chaîne, les deux opérandes sont convertis en chaînes. Ce processus utilise l'opération abstraite, interne ToString(note: en majuscule), et est distinct de la toStringméthode sur l'objet (ou sa chaîne prototype).
Enfin, les chaînes résultantes sont concaténées.
Détails
Sur le prototype de chaque objet de fonction constructeur correspondant à chaque type de langage en JavaScript (c'est-à-dire Number, BigInt, String, Boolean, Symbol et Object), il existe deux méthodes: valueOfet toString.
Le but de valueOfest de récupérer la valeur primitive associée à un objet (s'il en a une). Si un objet n'a pas de valeur primitive sous-jacente, l'objet est simplement renvoyé.
Si valueOfest invoqué contre une primitive, alors la primitive est automatiquement encadrée de la manière normale et la valeur primitive sous-jacente est renvoyée. Notez que pour les chaînes, la valeur primitive sous-jacente (c'est-à-dire la valeur renvoyée par valueOf) est la représentation sous forme de chaîne elle-même.
Le code suivant montre que la valueOfméthode renvoie la valeur primitive sous-jacente à partir d'un objet wrapper et montre comment les instances d'objet non modifiées qui ne correspondent pas aux primitives n'ont pas de valeur primitive à renvoyer, elles se renvoient donc simplement elles-mêmes.
console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)
Le but de toString, d'autre part, est de retourner une représentation sous forme de chaîne d'un objet.
Par exemple:
console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'
Pour la plupart des opérations, JavaScript tentera silencieusement de convertir un ou plusieurs opérandes dans le type requis. Ce comportement a été choisi pour faciliter l'utilisation de JavaScript. Au départ, JavaScript n'avait pas d'exceptions , et cela a peut-être également joué un rôle dans cette décision de conception. Ce type de conversion de type implicite est appelé coercition de type, et c'est la base du système de type lâche (faible) de JavaScript. Les règles compliquées derrière ce comportement visent à déplacer la complexité de la conversion de type dans le langage lui-même et hors de votre code.
Pendant le processus coercitif, deux modes de conversion peuvent se produire:
- Conversion d'un objet en primitive (qui peut impliquer une conversion de type elle-même), et
- La conversion directe à une instance de type spécifique, en utilisant un objet fonction constructeur d'un des types primitifs (ie.
Number(), Boolean(), String()Etc.)
Conversion en primitive
Lors de la tentative de conversion de types non primitifs en primitives sur lesquelles opérer, l'opération abstraite ToPrimitiveest appelée avec un "indice" facultatif de "nombre" ou "chaîne". Si le conseil est omis, le conseil par défaut est 'number' (sauf si la @@toPrimitiveméthode a été remplacée). Si l'indice est 'string', alors toStringest essayé en premier, et en valueOfsecond lieu si toStringn'a pas renvoyé de primitive. Sinon, vice-versa. L'indication dépend de l'opération qui demande la conversion.
L'opérateur d'addition ne fournit aucun indice, il valueOfest donc essayé en premier. L'opérateur de soustraction fournit un indice de «nombre», donc valueOfest essayé en premier. Les seules situations que je peux trouver dans la spécification dans lesquelles l'indication est 'string' sont:
Object#toString
- L'opération abstraite
ToPropertyKey, qui convertit un argument en une valeur qui peut être utilisée comme clé de propriété
Conversion directe de type
Chaque opérateur a ses propres règles pour terminer son opération. L'opérateur d'addition utilisera d'abord ToPrimitivepour s'assurer que chaque opérande est une primitive; puis, si l'un des opérandes est une chaîne, il invoquera alors délibérément l'opération abstraite ToStringsur chaque opérande, pour fournir le comportement de concaténation de chaînes que nous attendons avec les chaînes. Si, après l' ToPrimitiveétape, les deux opérandes ne sont pas des chaînes, une addition arithmétique est effectuée.
Contrairement à l'addition, l'opérateur de soustraction n'a pas de comportement surchargé, et donc invoquera toNumericsur chaque opérande les ayant d'abord convertis en primitives en utilisant ToPrimitive.
Alors:
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
Notez que l' Dateobjet intrinsèque est unique, en ce sens qu'il est le seul intrinsèque à remplacer la @@toPrimitiveméthode par défaut , dans laquelle l'indice par défaut est présumé être «chaîne» (plutôt que «nombre»). La raison en est que les Dateinstances se traduisent par défaut en chaînes lisibles, au lieu de leur valeur numérique, pour la commodité du programmeur. Vous pouvez remplacer @@toPrimitivevos propres objets en utilisant Symbol.toPrimitive.
La grille suivante montre les résultats de la coercition pour l'opérateur d'égalité abstraite ( ==) ( source ):

Voir aussi .
window.console.log (x);oualert (x);?