I’ve been working a lot with JavaScript lately, and in doing so, have come across some not very obvious caveats with how it works.
The first confusing issue I ran into is sort stability with different browsers.
Sort Stability
You can read the Wikipedia article on sort stability here for the full story: http://en.wikipedia.org/wiki/Sorting_algorithm#Stability. But in short, if a sort is stable, then when the algorithm runs across two items which are equal, they will remain in the same order they were in the original list. In an unstable sort, the results will be indeterminate. Unfortunately, the implementation for the sort()
function in JavaScript differs in each browser.
Let’s look at a specific example:
<html>
<body>
<button onclick="myFunction()">Sort</button>
<p id="demo"></p>
<script>
var points = [40, 100, 1, 5, 25, 10, 30, 60, 50, 70, 12, 14, 56, 33];
document.getElementById("demo").innerHTML = points;
function myFunction() {
points.sort(function(a, b){return 0});
document.getElementById("demo").innerHTML = points;
}
</script>
</body>
</html>
Here we have an array of numbers, and every time you click the ‘Sort’ button, it will do a sort where all the items are deemed equal. In Chrome, pressing this button yields different results every time:
40,100,1,5,25,10,30,60,50,70,12,14,56,33
60,40,1,5,25,10,30,100,50,70,12,14,56,33
100,60,1,5,25,10,30,40,50,70,12,14,56,33
However, in Internet Explorer, when you press the button, there is no change, it’s always:
40,100,1,5,25,10,30,60,50,70,12,14,56,33
This can be fairly frustrating, especially if you’re using an array that has some equal items to determine UI positioning, as things will keep jumping around. Here is an overview of the current browser condition on sort stability:
It’s important to keep this in mind whenever you are using sort()
in JavaScript. If you need a stable sort, it is possible to get it from an unstable sort, but it requires some work. One way is to implement a common sort algorithm, such as Merge Sort, or use a library that has it. Another way is to extend and use positioning information in addition to value comparison, like outlined in this blog: http://blog.vjeux.com/2010/javascript/javascript-sorting-table.html.
Sorting Numbers
There is an unfortunate behaviour in the sort()
implementation where it treats all numbers as Unicode strings. This is outlined on MDN (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), but it’s something that I personally often forget, because it is, in my opinion, unexpected behaviour.
For example, if you use this code:
var scores = [1, 2, 10, 21];
scores.sort();
You would expect the numbers to stay in the same order, as they are sorted. Unfortunately, the result is:
[1, 10, 2, 21]
Why? Well the answer is that the numbers are converted to strings, and then compared, which results in Unicode comparison where "10" comes before "20."
The easiest way around this is to always provide the delegate when sorting numbers to force JavaScript conversion. Something like:
scores.sort(function (a, b) { return a - b;});
This will result in the properly sorted numbers.
Null, undefined, 0, ‘’, false, NaN
In JavaScript, one very common way to check if something actually exists or not is to use code like:
if (myvar) {
It’s also very useful for variable instantiation from parameters:
function (p1) { p1 = p1 || defaultValue;
It’s important to note though, that all of the values defined in the title (null, undefined, 0, ‘’, false, NaN) will always return false from a check like above. This means using a simple check like this will not distinguish between a variable which is false, and variable which was never set. One easy way to enforce a true Boolean is to do something like:
myvar = !!myvar
Or of course, the type equality:
if (myvar === true) {
This can be especially dangerous if you have a variable which defaults to true, but you use the type check as above to default the variable:
function (p1) { var p1 = p1 || true;
This code will result in the property being true if the user passed nothing, or if the user passed false explicitly.
Arguments Object isn’t Actually an Array
This last one really got me. If you check MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments), it says that arguments is "An Array-like object corresponding to the arguments passed to a function." The important part here being Array-like.
Since it’s encourage to duck-type in JavaScript, you might be tempted to use the .length
property, which would exist and be accurate. But unfortunately, that small similarity is only there to confuse you. That, and a 0th based index on items, is the only similarity it has to an Array. If you attempt to use pop()
, or slice()
, it will fail with undefined function.
The easiest way to get around this is to always convert arguments to a true Array after you get it. You can do this with some clever code, such as:
var argumentsAsArray = [].splice.call(arguments, 0);
I don’t know why it wasn’t just made to be an Array.