Click here to Skip to main content
15,394,438 members
Articles / Programming Languages / Javascript
Tip/Trick
Posted 13 Jun 2014

Tagged as

Stats

6.1K views
4 bookmarked

JavaScript: Some not-so-obvious caveats

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
13 Jun 2014CPOL4 min read
An issue I ran into is sort stability with different browsers.

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) { // code that uses myvar }

It’s also very useful for variable instantiation from parameters:

function (p1) { p1 = p1 || defaultValue; // use p1 here as it will always be set }

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) { // code that only executes if myvar is really 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; // use p1 here }

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Terrence Sheflin
Team Leader
Canada Canada
I am a Lead Software Architect (UI/UX), focusing mainly on developing with JavaScript, C#, ASP.NET WebApi and MVC.

Comments and Discussions

 
-- There are no messages in this forum --