JavaScript for C# developers: coercion

In C#, we have implicit and explicit conversions. In both cases the idea is that we, the readers of the code, are not surprised by any conversions that happen. That’s why we can freely intermix ints with floats in a floating point calculation and everything turns out just fine. The ints are implicitly converted to floats (there’s no data loss) and the calculation comes out right. However, when there’s a possibility of some kind of data loss (say, converting a ulong to a long variable) you have to explicitly state the conversion in order to say “yep, I know what I’m doing; move along, nothing to see here.” Of course, the explicit conversion does come with an implied contract – that you have, you know, actually determined that the possible loss of data is benign – but otherwise just have at it.

Old pepper millIn JavaScript type conversions are known as coercions. Variables will be coerced automatically into values of another type to make the statement or expression work. Unlike C#’s implicit conversions where there’s no data loss, JavaScript’s coercions can really wreak havoc if you are not paying attention.

One great example I’ve already talked about is the use of the double-equals (equality) comparison operator. If the two sides are evaluated to be of different types, JavaScript will coerce one or both sides to make sense of the comparison. So we get things like this:

var b;
b = 0 == "0"; // true, the "0" is coerced to 0
b = 0 == ""; // true, the "" is coerced to 0
b = "0" == ""; // false, duh, both sides are the same type

var o = {
  toString: function () {
    return "42";
  }
};
b = o == "42"; // true, the object is coerced to a string using toString()

As you can see, the first three assignments show that ‘==’ is not even transitive, going against everything you’ve ever learned; which, if nothing else, should keep you up at night. That’s why the recommendation is to always use triple-equals (identity) as a comparison operator since it does not do any coercions at all. As a handy hint, if you really, really want to use “==”, then you can force the coercion you want rather than the one you get:

var b;
b = "" + x == "" + y; // String comparison
b = +x == +y;         // Number comparison
b = !x == !y;         // Boolean comparison

But really, forget I told you this and just use “===”. Really – you can just thank me later.

At CodeMash 2012, Gary Bernhardt did an awesomely funny presentation called Wat. Watch it, and then come back here. I’ll wait.

The JavaScript parts of Gary’s talk are all to do with coercions. The first one was “what’s [] + []?”, or, in other words, what’s an empty array plus an empty array? The answer is the empty string, and the reason is not that difficult when you think in terms of coercion. The binary “+” operator in JavaScript, like C#, has two meanings: add a number to a number, and concatenate a string to a string. In C#, that’s all there is to it: you’ve got to make sure the types match and you’re done. However, in JavaScript, if the left-hand side and the right-hand side don’t make sense when used with “+”, they get coerced, usually to strings. Since [].toString() is the empty string (toString() on an array will list the contents of the array as strings separated by commas), you get the result Gary showed.

The second example, [] + {}, should now be obvious. Again, values are getting coerced to strings using the toString() method. (The answer is the string “[object Object]”, the default result of calling Object.prototype.toString().)

The third example, {} + [], caught me out at first. To my mind, it should be the same as the second example. This is where it gets seriously wacky. This code:

var result = {} + [];
console.log(result); // "[object Object]
console.log(typeof result); // "string"

produces the results shown in the comments; exactly the same as before. Yet, typing {} + [] in the Console in Firebug produces Gary’s answer of 0. What’s going on?

The answer is that the first {} in the statement is not evaluated as an object, but as a block. It’s an empty block (open brace, no code, close brace) and does nothing. So we’re left with +[]. The unary plus is numeric only, and so the empty array gets coerced to a number, in fact to 0. (In essence, it’s coerced to an empty string, and coercing an empty string to a number produces 0.)

The fourth example ({} + {}) is (a) an empty block as before, and (b) an object coerced to a Number (OK, it coerces to “[object Object]” as a string,  and that string, when coerced to a number, produces NaN).

Mind you, if you ever rely on any of this stuff, please let me know so that I don’t work on the same code as you. Thanks!

Album cover for Classic SongsNow playing:
Taylor, James - Handy Man
(from Classic Songs)


Loading similar posts...   Loading links to posts on similar topics...

No Responses

Feel free to add a comment...

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response