Some Obscure JavaScript Pitfalls
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](http://stackoverflow.com/questions/3895478/does-javascript-have-a-range-equivalent) 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](http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop) 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](https://github.com/substack/json-stable-stringify) node library to address this issue when writing the functional caching library [souvenir](https://github.com/MakerStudios/souvenir). The [language specification](http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.3) 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