Some Obscure JavaScript Pitfalls

2014-12-15 tech programming javascript

These are some (relatively) obscure JavaScript quirks which have caused issues for me in the past. While JavaScript has plenty of common pitfalls that most users have encountered and overcome, I’ve tried to leave them off of this list. Next time you’re scratching your head over your JavaScript code misbehaving, maybe one of these will come in handy!

  • Array::sort sorts alphabetically by default.

    This is often what you want, but not if you need to sort a list of numbers:

      > [ "d", "a", "b" ].sort();
      [ "a", "b", "d" ]
      > [ 2, 10, 1 ].sort();
      [ 1, 10, 2 ]
    

    In order to get around this, simply supply a comparator:

      > [ 2, 10, 1 ].sort(function(a, b) { return a - b; });
      [ 1, 2, 10 ]
    
  • The Array constructor is weird.

    Suppose you wanted to generate an array of the first 10 positive integers. You might try something like

      > (new Array(10)).map(function(_, index) { return index + 1; });
    

    This won’t work. new Array(10) creates an empty array and sets its length property to 10 but does not create any elements for Array::map to iterate over.

    There are ways to work around this particular example, but you may just want to avoid instantiating arrays with the Array constructor.

  • Casting to integer can be subject to 32-bit overflow.

    It’s often useful to cast a value x to integer, and some convenient ways of doing that include performing various bitwise (non-)operations on the variable, e.g. ~~x, x | 0, or x >> 0. However, these will overflow for integers greater than or equal to 2^31. The overflow makes these casting tricks slightly less useful, particularly when dealing with dates or large file sizes:

      > var d = Date.now();
      > d
      1417131147103
      > ~~d
      -208060577
    

    If you just want to coerce your variable x to a Number (but not necessarily an integer), you can use +x. Alternately, you can use the clunky parseInt (but don’t forget to supply a radix — otherwise, one might be chosen for you that you don’t expect).

  • Object enumeration order is not well-defined.

    Fundamentally, an object in JavaScript is an unordered collection. When looping through the keys of an object, you cannot depend on the order. There is a great thread on Stack Overflow with further discussion and some examples of how enumeration behavior differs among different JavaScript runtimes.

    There’s a good argument to be made that you can ignore this. On the server side, that may be the case, especially if you’re not doing anything weird with your objects (i.e. giving them numeric-looking names, delete-ing keys from them). The node async library, for example, lets you pass in an object to the async.series method, to be executed (ostensibly) in the order written. This can be nice because you can give each step a descriptive name (I would personally use async.auto instead). If you understand the risks, feel free to throw caution to the wind.

    However, in the browser, it would be a mistake. You shouldn’t expect objects to enumerate in the same way for all of your users. Use an array instead.

    Note that I use “well-defined” in the mathematical sense (equivalent inputs giving the same output); it’s not a dig at any person or thing.

  • JSON.stringify is not well-defined.

    Because an object has no specific order, you also shouldn’t depend on always getting the same result from operating on it with JSON.stringify. This is typically a non-issue, but it’s sometimes useful to have a consistent JSON output (e.g. when comparing two objects for equality). I have had occasion to use the json-stable-stringify node library to address this issue when writing the functional caching library souvenir.

    The language specification actually seems to stipulate that JSON.stringify must enumerate objects the same way a for-in loop does (cf. the reference to Object.keys in the linked section, 15.12.3), so this pitfall is a corollary of the one above.

  • Date::getHours, etc., of limited usefulness.

    A date object exposes methods like getDate and getHours, but also getUTCDate and getUTCHours. The former return values in local time. System code (e.g. server-side or, generically, non-user-facing code) should use the UTC alternatives, so there isn’t an implicit reference to the time zone of the computer executing the code. getHours, etc. are useful in the browser when displaying things to a user in his or her local time, but not for much else.

    Next time your unit test suite inexplicably breaks on the morning after daylight savings, audit your codebase for local time methods! :)

  • Array::slice vs. Array::splice.

    The array prototype has these two separate functions, slice and splice, that perform similar tasks (returning a sub-array). The key difference is that splice modifies the array, while slice does not. Because the names are so similar, it’s easy to get them mixed up. I end up using slice a lot more often than splice because I try to treat my data as immutable whenever possible.

comments powered by Disqus