Introduction

This article is an introduction to the weird side of JavaScript, and it definitely has a weird side! Software developers who usually write code in another language will find a lot of intriguing "features" when they begin to write code in the world's most widely used language. Hopefully, even seasoned JavaScript developers will find some new gotchas in this article that they can watch out for. Enjoy!

Functions and Operators

Double-equals

The == operator performs a comparison with type coercion. This means that you can compare objects of two different types and it will attempt to convert them to the same type before performing a comparison. For example:

"1" == 1//true

However, this is very often misleading, and never needed. In the case above, you can convert the String to a Number and use the type-sensitive triple-equals:

Number("1") === 1; //true

Or, better still, ensure your operands are of the correct type in the first place.

Due to its type-coercing behaviour, double-equals frequently breaks the transitivity rule, which is a little bit scary:

"" == 0//true - empty string is coerced to Number 0.
0 == "0"//true - Number 0 is coerced to String "0"
"" == "0"//false - operands are both String so no coercion is done.

The above comparisons will all yield false when triple-equals is used.

parseInt doesn't assume base-10

If you omit the second parameter in a call to parseInt, then the base will be determined according to the following rules:

By default, assume a radix 10.

If the number begins with 0x then assume radix 16.

If the number begins with 0 then assume radix 8.

The common mistake is to allow a user-input or suchlike which begins with a 0. Radix 8 (octal) is then used and we see this effect:

parseInt("8"); //8
parseInt("08"); //0

Therefore, always include the second parameter:

parseInt("8", 10); //8
parseInt("08", 10); //8

ECMAScript5 side note: ES5 no longer includes the radix-8 assumption. Additionally, omission of the second parameter helpfully raises a JSLint warning.

String replace

The string replace function only replaces the first match, not all matches as you may expect.

The global modifier ensures that the replacement does not stop after the first match.

The "+" Operator Both Adds and Concatenates

PHP, another loosely typed language, has the '.' operator for string concatenation. JavaScript does not - so "a + b" always results in concatenation when either of the operands is a string. This might catch you out if you're trying to add a number to, say, the contents of an input element (which will be a string), so you need to first cast to Number:

You will get the same result (typeof = "object") when you use this operator against instances of your own objects.

As a side note, "typeof null" yields "object", which is a bit weird.

instanceof

Returns whether the object (or an object in its prototype chain) was constructed using the specified constructor, which is useful when attempting to check the type of one of your own defined object types. However, it is pretty misleading if you create an instance of one of the built-in types using literal syntax:

Phew! So in summary, if you want to test the type of a Boolean, String, Number, or Function, you can use typeof. For anything else, you can use instanceof.

Oh, one more thing. Within a function, there is a predefined variable, "arguments", which gives an array of arguments passed to the function. However, it's not really an array, it's an array-like object with a length property, and properties from 0 - length. Pretty strange...but you can convert it to a real array using this common trick:

var args = Array.prototype.slice.call(arguments, 0);

The same goes for NodeList objects returned by DOM calls such as getElementsByTagName - they can also be converted to proper arrays using the above code.

eval

eval interprets a string as code but its use is generally frowned upon. It's slow - when JavaScript is loaded into the browser, it gets compiled into native code; however, every time an eval statement is reached during execution, the compilation engine has to be started up all over again, and that is quite expensive. It looks quite ugly, too, because in most cases it is misused. Additionally, the code being eval'd is executed in the current scope, so it can modify local variables and add stuff to your scope which may be unintended.

Parsing JSON is a common usage; normally people will use "var obj = eval(jsonText);", however almost all browsers now support the native JSON object which you can use instead: "var obj = JSON.parse(jsonText);". There is also the JSON.stringify function for the opposite use. Even better, you can use the jQuery.parseJSON function which will always work.

The setTimeout and setInterval functions can take a string as its first parameter which will be interpreted, so that shouldn't be done either. Use an actual function as that parameter.

Finally, the Function constructor is much the same as eval - the only difference being that it will be executed in the global context.

with

The with statement gives you a shorthand for accessing the properties of an object, and there are conflicting views on whether it should be used. Douglas Crockford doesn't like it. John Resig finds a number of clever uses for it in his book, but also concedes that it has a performance hit and can be a little confusing. Looking at a with block in isolation, it isn't possible to tell exactly what is happening. For example:

with (obj) {
bob = "mmm";
eric = 123;
}

Did I just modify a local variable called "bob", or did I set obj.bob? Well, if obj.bob is already defined, then it is reset to "mmm". Otherwise, if there is another bob variable in scope, it is changed. Otherwise the global variable bob is set. In the end, it is just clearer to write exactly what you mean to do.

obj.bob = "mmm";
obj.eric = 123;

ECMAScript5 side note: ES5 strict mode will not support the with statement.

Math.min and Math.max

These are two utility functions that give you the max or min of the arguments. However, if you use them without any arguments, you get:

Math.max(); // -Infinity
Math.min(); // Infinity

It does make sense really, since Math.max() is returning the largest of an empty list of numbers. But if you're doing that, you're probably after Number.MAX_VALUE or Number.MIN_VALUE.

Types and Constructors

Constructing Built-in Types with the 'new' Keyword

JavaScript has the types Object, Array, Boolean, Number, String, and Function. Each has its own literal syntax and so the explicit constructor is never required.

Explicit (bad)

Literal (good)

var a = new Object();a.greet = "hello";

var a = { greet: "hello" };

var b = new Boolean(true);

var b = true;

var c = new Array("one", "two");

var c = ["one", "two"];

var d = new String("hello");

var d = "hello"

var e = new Function("greeting", "alert(greeting);");

var e = function(greeting) { alert(greeting); };

However, if you use the new keyword to construct one of these types, what you actually get is an object of type Object that inherits the prototype of the type you want to construct (the exception is Function). So although you can construct a Number using the new keyword, it'll be of type Object;

Calling a function with the new keyword creates a new object and then calls the function with that new object as its context. The object is then returned. Conversely, invoking a function without 'new' will result in the context being the global object if that function is not invoked on an object (which it won't be anyway if it's used as a constructor!)

The danger in accidentally forgetting to include 'new' means that a number of alternative object-constructing patterns have emerged that completely remove the requirement for this keyword, although that's beyond the scope of this article, so I suggest some further reading!

There is no Integer

Numerical calculations are comparatively slow because there is no Integer type, only Number - and Number is an IEEE floating point double-precision (64 bit) type. This means that Number exhibits the floating point rounding error:

0.1 + 0.2 === 0.3//false

Since there's no distinction between integers and floats, unlike C# or Java, this is true:

0.0 === 0; //true

Finally a Number puzzle. How can you achieve the following?

a === b; //true
1/a === 1/b; //false

The answer is that the specification of Number allows for both positive and negative zero values. Positive zero equals negative zero, but positive infinity does not equal negative infinity:

When i is declared in the for loop, it remains in scope after the loop exits. So the final call to console.log will output 10. There is a JSLint warning which aims to avoid this confusion: forcing all variables to be declared at the start of a function so that it is obvious what is happening.

It is possible to create a scope by writing an immediately-executing function:

Global Variables

JavaScript has a global scope which you should use sparingly by namespacing your code. Global variables add a performance hit to your application, since when you access them, the runtime has to step up through each scope until it finds them. They can be accessed and modified either intentionally or by accident by your own code and other libraries. This leads to another, more serious issue - cross-site scripting. If a bad person figures out how to execute some code on your page, then they can also easily interfere with your application itself by modifying global variables. Inexperienced developers constantly add variables to the global scope by accident, and there are a few examples of how that happens throughout this article.

I have seen the following code which attempts to declare two local variables with the same value:

var a = b = 3;

This correctly results in a = 3 and b = 3, however a is in local scope and b is in global scope. "b = 3" is executed first, and the result of that global operation, 3, is then assigned to the local var a.

This code has the desired effect of declaring two local variables with value 3:

var a = 3,
b = a;

'this' and Inner Functions

The 'this' keyword always refers to the object on which the current function was called. However, if the function is not invoked on an object, such as is the case with inner functions, then 'this' is set to the global object (window).

However, we have closures in JavaScript so we can just take a reference to 'this' if we want to refer to it in the inner function.

ECMAScript5 side note: ES5 strict mode introduces a change to this functionality whereby 'this' will be undefined instead of being set to the global object.

Miscellaneous

The Absence of data: 'null' and 'undefined'

There are two object states that represent the lack of a value, null and undefined. This is pretty confusing for a programmer coming from another language like C#. You might expect the following to be true:

var a;
a === null; //false
a === undefined; //true

'a' is in fact undefined (although if you do a double-equals comparison with null then it will be true, but that is just another mistake that results in the code "seeming" to work).

If you want to check that a variable actually has a value, and also follow the rule of never using double-equals, you need to do the following:

if(a !== null && a !== undefined) {
...
}

Or, you could do this, which is exactly the same thing (although JSLint will unfortunately complain at you):

if (a != null) {
...
}

"Aha!", you might say. null and undefined are both falsy (i.e., coerced to false) so you can do this:

if(a) {
...
}

But, of course, zero is also falsy, and so is the empty string. If one of those is a valid value for 'a', then you will have to use the former, more verbose option. The shorter option can always be used for objects, arrays, and booleans.

Redefining Undefined

That's right, you can redefine undefined, as it isn't a reserved word:

undefined = "surprise!";

However, you can get it back by assigning an undefined variable or using the "void" operator (which is otherwise pretty useless):

undefined = void0;

...and that's why the first line of the jQuery library is:

(function ( window, undefined ) {
... // jQuery library!
}(window));

That function is called with a single parameter ensuring that the second parameter, undefined, is in fact undefined.

By the way, you can't redefine null - but you can redefine NaN, Infinity, and the constructor functions for the built-in types. Try this:

Array = function (){ alert("hello!"); }
var a = new Array();

Of course, you should be using the literal syntax to declare an Array anyway.

Optional Semicolons

Semicolons are optional so that it's easier for beginners to write, but it's pretty yucky to leave them out and doesn't really make anything easier for anyone. The result is that when the interpreter gets an error, it backtracks and tries to guess where the semicolon is supposed to go.

The above code does not return an object, it returns undefined - and no error is thrown. A semicolon is automatically inserted immediately after the return statement. The remainder of the code is perfectly valid, even if it does not do anything, and this should be evidence enough that in JavaScript, an opening curly brace should go at the end of the current line instead of a new line. It isn't just a matter of style! This code returns an object with a single property "a":

return {
a: "hello"
};

NaN

The type of NaN (Not a Number) is... Number.

typeof NaN === "number"//true

Additionally, NaN compared to anything is false:

NaN === NaN; // false

Since you can't do a NaN comparison, the only way to test whether a number is equal to NaN is with the helper function isNaN.

As a side note, we also have the helper function isFinite, which returns false when the argument is NaN or Infinity.

The 'arguments' Object

Within a function, you can reference the arguments object to retrieve the list of arguments. The first gotcha is that this object is not an Array but an array-like object (which is an object with a length property, and a value at [length-1]). To convert to a proper array, you can use the array splice function to create an array from the properties in arguments:

The second gotcha is that when a function has explicit arguments in its signature, they can be reassigned and the arguments object will also be changed. This indicates that the arguments object points to the variables themselves as opposed to their values; you can't rely on arguments to give you their original values.

Share

About the Author

Repstor’s products enable the successful adoption of content management systems within organizations. Repstor affinity provides uninterrupted access to content systems, like SharePoint through the familiar interface of Microsoft Outlook. Repstor assist provides facilitated filing for all users by suggesting the most suitable location for new content based on best practice filing.

I only mention it because I have been bitten by it. I had a problem very similar to the Google example I linked. It was a real pain for me to understand what the problem was! Also, closures can expose memory leaks (see IBM's or Mozilla's articles on the subject).

How can this give three different results? The trick is the duality of the '+' operator and the automagic methods valueOf and toString.

Each reference to 'a' in the alerts gets the object 'a', not a value. But if a numeric value is required (e.g. when multiplying or adding numbers), JavaScript automatically calls any valueOf method for the object; however, if a string is required (e.g. when concatenating to another string), JavaScript calls any toString method for the object. The double whammy is that I have not defined a toString method, so when a string is needed, JavaScript calls the valueOf method and converts the result to a string.

'a's valueOf function gets an incrementing number, not a fixed number - this is the key to the different results.
In all three examples, the sub expression 'a = ' + a is interpretted as 'a = ' + a.toString() (or effectively 'a = ' + a.valueOf().toString()); which increments 'a's internal value from 0 to 1.

In case 1, (3 * a) is interpretted as (3 * a.valueOf()) as '*' works with numbers, causing a value of 2 (one more than 1) to be return so 3 * a = 3 * 2 = 6.

In case 2, a + a + a is interpretted as concatenating three strings (as it is added to the ', a + a + a = ' subexpression which is a string). The three calls of a.toString() returning 2, 3, and 4, so a + a + a = 2 + 3 + 4 = 234 (string concatentation).

In case 3, (a + a + a) is a subexpression as it is in parentheses, all of whose components could be numbers so the subexpression evaluates to (a.valueOf() + a.valueOf() + a.valueOf()) which comes out as (2 + 3 + 4) = 9. The result is concatentated to the string.