10 Oddities And Secrets About JavaScript
JavaScript. At once bizarre and yet beautiful, it is surely the programming language that Pablo Picasso would have invented. Null is apparently an object, an empty array is apparently equal to false
, and functions are bandied around as though they were tennis balls.
This article is aimed at intermediate developers who are curious about more advanced JavaScript. It is a collection of JavaScript’s oddities and well-kept secrets. Some sections will hopefully give you insight into how these curiosities can be useful to your code, while other sections are pure WTF material. So, let’s get started.
Data Types And Definitions
1. Null is an Object
Let’s start with everyone’s favorite JavaScript oddity, as well known as it is. Null is apparently an object, which, as far as contradictions go, is right up there with the best of them. Null? An object? “Surely, the definition of null is the total absence of meaningful value,” you say. You’d be right. But that’s the way it is. Here’s the proof:
alert(typeof null); //alerts 'object'
Despite this, null is not considered an instance of an object. (In case you didn’t know, values in JavaScript are instances of base objects. So, every number is an instance of the Number
object, every object is an instance of the Object
object, and so on.) This brings us back to sanity, because if null is the absence of value, then it obviously can’t be an instance of anything. Hence, the following evaluates to false
:
alert(null instanceof Object); //evaluates false
2. NaN is a Number
You thought null being an object was ridiculous? Try dealing with the idea of NaN
— “not a number” — being a number! Moreover, NaN
is not considered equal to itself! Does your head hurt yet?
alert(typeof NaN); //alerts 'Number'
alert(NaN === NaN); //evaluates false
In fact NaN
is not equal to anything. The only way to confirm that something is NaN
is via the function isNaN()
.
3. An Array With No Keys == False (About Truthy and Falsy)
Here’s another much-loved JavaScript oddity:
alert(new Array() == false); //evaluates true
To understand what’s happening here, you need to understand the concepts of truthy and falsy. These are sort of true/false-lite, which will anger you somewhat if you majored in logic or philosophy.
I’ve read many explanations of what truthy and falsy are, and I feel the easiest one to understand is this: in JavaScript, every non-boolean value has a built-in boolean flag that is called on when the value is asked to behave like a boolean; like, for example, when you compare it to a boolean.
Because apples cannot be compared to pears, when JavaScript is asked to compare values of differing data types, it first “coerces” them into a common data type. False
, zero
, null
, undefined
, empty strings and NaN
all end up becoming false
— not permanently, just for the given expression. An example to the rescue:
var someVar = 0;
alert(someVar == false); //evaluates true
Here, we’re attempting to compare the number 0
to the boolean false
. Because these data types are incompatible, JavaScript secretly coerces our variable into its truthy or falsy equivalent, which in the case of 0
(as I said above) is falsy.
You may have noticed that I didn’t include empty arrays in the list of falsies above. Empty arrays are curious things: they actually evaluate to truthy but, when compared against a boolean, behave like a falsy. Confused yet? With good cause. Another example perhaps?
var someVar = []; //empty array
alert(someVar == false); //evaluates true
if (someVar) alert('hello'); //alert runs, so someVar evaluates to true
To avoid coercion, you can use the value and type comparison operator, ===
, (as opposed to ==
, which compares only by value). So:
var someVar = 0;
alert(someVar == false); //evaluates true – zero is a falsy
alert(someVar === false); //evaluates false – zero is a number, not a boolean
Phew. As you’ve probably gathered, this is a broad topic, and I recommend reading up more on it — particularly on data coercion, which, while not uniquely a JavaScript concept, is nonetheless prominent in JavaScript.
I discuss the concept of truthy and falsy and data coercion more over here. And if you really want to sink your teeth into what happens internally when JavaScript is asked to compare two values, then check out section 11.9.3 of the ECMA-262 document specification.
Regular Expressions
4. replace() Can Accept a Callback Function
This is one of JavaScript’s best-kept secrets and arrived in v1.3. Most usages of replace()
look something like this:
alert('10 13 21 48 52'.replace(/d+/g, '*')); //replace all numbers with *
This is a simple replacement: a string, an asterisk. But what if we wanted more control over how and when our replacements take place? What if we wanted to replace only numbers under 30? This can’t be achieved with regular expressions alone (they’re all about strings, after all, not maths). We need to jump into a callback function to evaluate each match.
alert('10 13 21 48 52'.replace(/d+/g, function(match) {
return parseInt(match) < 30 ? '*' : match;
}));
For every match made, JavaScript calls our function, passing the match into our match argument. Then, we return either the asterisk (if the number matched is under 30) or the match itself (i.e. no match should take place).
5. Regular Expressions: More Than Just Match and Replace
Many intermediate JavaScript developers get by just on match
and replace
with regular expressions. But JavaScript defines more methods than these two.
Of particular interest is test()
, which works like match
except that it doesn’t return matches: it simply confirms whether a pattern matches. In this sense, it is computationally lighter.
alert(/w{3,}/.test('Hello')); //alerts 'true'
The above looks for a pattern of three or more alphanumeric characters, and because the string Hello
meets that requirement, we get true
. We don’t get the actual match, just the result.
Also of note is the RegExp
object, by which you can create dynamic regular expressions, as opposed to static ones. The majority of regular expressions are declared using short form (i.e. enclosed in forward slashes, as we did above). That way, though, you can’t reference variables, so making dynamic patterns is impossible. With RegExp()
, though, you can.
function findWord(word, string) {
var instancesOfWord = string.match(new RegExp('b'+word+'b', 'ig'));
alert(instancesOfWord);
}
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');
Here, we’re making a dynamic pattern based on the value of the argument word
. The function returns the number of times that word
appears in string as a word in its own right (i.e. not as a part of other words). So, our example returns car
once, ignoring the car
tokens in the words Carl
and card
. It forces this by checking for a word boundary (b
) on either side of the word that we’re looking for.
Because RegExp
are specified as strings, not via forward-slash syntax, we can use variables in building the pattern. This also means, however, that we must double-escape any special characters, as we did with our word boundary character.
Functions And Scope
6. You Can Fake Scope
The scope in which something executes defines what variables are accessible. Free-standing JavaScript (i.e. JavaScript that does not run inside a function) operates within the global scope of the window
object, to which everything has access; whereas local variables declared inside functions are accessible only within that function, not outside.
var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); }
getAnimal('lovely'); //alerts 'lovely dog';
Here, our variable and function are both declared in the global scope (i.e. on window
). Because this always points to the current scope, in this example it points to window
. Therefore, the function looks for window.animal
, which it finds. So far, so normal. But we can actually con our function into thinking that it’s running in a different scope, regardless of its own natural scope. We do this by calling its built-in call()
method, rather than the function itself:
var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); };
var myObj = {animal: 'camel'};
getAnimal.call(myObj, 'lovely'); //alerts 'lovely camel'
Here, our function runs not on window
but on myObj
— specified as the first argument of the call method. Essentially, call()
pretends that our function is a method of myObj
(if this doesn’t make sense, you might want to read up on JavaScript’s system of prototypal inheritance). Note also that any arguments we pass to call()
after the first will be passed on to our function — hence we’re passing in lovely
as our adjective
argument.
I’ve heard JavaScript developers say that they’ve gone years without ever needing to use this, not least because good code design ensures that you don’t need this smoke and mirrors. Nonetheless, it’s certainly an interesting one.
As an aside, apply()
does the same job as call()
, except that arguments to the function are specified as an array, rather than as individual arguments. So, the above example using apply()
would look like this:
getAnimal.apply(myObj, ['lovely']); //func args sent as array
7. Functions Can Execute Themselves
There’s no denying it:
(function() { alert('hello'); })(); //alerts 'hello'
The syntax is simple enough: we declare a function and immediately call it just as we call other functions, with ()
syntax. You might wonder why we would do this. It seems like a contradiction in terms: a function normally contains code that we want to execute later, not now, otherwise we wouldn’t have put the code in a function.
One good use of self-executing functions (SEFs) is to bind the current values of variables for use inside delayed code, such as callbacks to events, timeouts and intervals. Here is the problem:
var someVar = 'hello';
setTimeout(function() { alert(someVar); }, 1000);
var someVar = 'goodbye';
Newbies in forums invariably ask why the alert
in the timeout
says goodbye
and not hello
. The answer is that the timeout
callback function is precisely that — a callback — so it doesn’t evaluate the value of someVar
until it runs. And by then, someVar
has long since been overwritten by goodbye
.
SEFs provide a solution to this problem. Instead of specifying the timeout callback implicitly as we do above, we return it from an SEF, into which we pass the current value of someVar
as arguments. Effectively, this means we pass in and isolate the current value of someVar
, protecting it from whatever happens to the actual variable someVar
thereafter. This is like taking a photo of a car before you respray it; the photo will not update with the resprayed color; it will forever show the color of the car at the time the photo was taken.
var someVar = 'hello';
setTimeout((function(someVar) {
return function() { alert(someVar); }
})(someVar), 1000);
var someVar = 'goodbye';
This time, it alerts hello
, as desired, because it is alerting the isolated version of someVar
(i.e. the function argument, not the outer variable).
The Browser
8. Firefox Reads and Returns Colors in RGB, Not Hex
I’ve never really understood why Mozilla does this. Surely it realizes that anyone interrogating computed colors via JavaScript is interested in hex format and not RGB. To clarify, here’s an example:
Hello, world!
<script>
var ie = navigator.appVersion.indexOf('MSIE') != -1;
var p = document.getElementById('somePara');
alert(ie ? p.currentStyle.color : getComputedStyle(p, null).color);
</script>
While most browsers will alert ff9900
, Firefox returns rgb(255, 153, 0)
, the RGB equivalent. Plenty of JavaScript functions are out there for converting RGB to hex.
Note that when I say computed color, I’m referring to the current color, regardless of how it is applied to the element. Compare this to style, which reads only style properties that were implicitly set in an element’s style attribute. Also, as you’ll have noticed in the example above, IE has a different method of detecting computed styles from other browsers.
As an aside, jQuery’s css()
method encompasses this sort of computed detection, and it returns styles however they were applied to an element: implicitly or through inheritance or whatever. Therefore, you would relatively rarely need the native getComputedStyle
and currentStyle
.
Miscellaneous
9. 0.1 + 0.2 !== 0.3
This one is an oddity not just in JavaScript; it’s actually a prevailing problem in computer science, and it affects many languages. The output of this is 0.30000000000000004.
This has to do with an issue called machine precision. When JavaScript tries to execute the line above, it converts the values to their binary equivalents.
This is where the problem starts. 0.1 is not really 0.1 but rather its binary equivalent, which is a near-ish (but not identical) value. In essence, as soon as you write the values, they are doomed to lose their precision. You might have just wanted two simple decimals, but what you get, as Chris Pine notes, is binary floating-point arithmetic. Sort of like wanting your text translated into Russian but getting Belorussian. Similar, but not the same.
More is going on here, but it’s beyond the scope of this article (not to mention the mathematical capabilities of this author).
Workarounds for this problem are a favorite on computer science and developer forums. Your choice, to a point, comes down to the sort of calculations you’re doing. The pros and cons of each are beyond the scope of this article, but the common choice is between the following:
- Converting to integers and calculating on those instead, then converting back to decimals afterward; or
- Tweaking your logic to allow for a range rather than a specific result.
So, for example, rather than…
var num1 = 0.1, num2 = 0.2, shouldEqual = 0.3;
alert(num1 + num2 == shouldEqual); //false
… we would do this:
alert(num1 + num2 > shouldEqual - 0.001 && num1 + num2 < shouldEqual + 0.001); //true
Translated, this says that because 0.1 + 0.2 is apparently not 0.3, check instead that it’s more or less 0.3 — specifically, within a range of 0.001 on either side of it. The obvious drawback is that, for very precise calculations, this will return inaccurate results.
10. Undefined Can Be Defined
OK, let’s end with a silly, rather inconsequential one. Strange as it might sound, undefined
is not actually a reserved word in JavaScript, even though it has a special meaning and is the only way to determine whether a variable is undefined. So:
var someVar;
alert(someVar == undefined); //evaluates true
So far, so normal. But:
undefined = "I'm not undefined!";
var someVar;
alert(someVar == undefined); //evaluates false!
You can also check Mozilla’s list of all reserved words in JavaScript for future reference.
Further Resources
- JavaScript Oddities Explained. Comparing.
- JavaScript Gotchas
- Yet More on Gotchas
- replace() callback functions
Further Reading
- JavaScript Profiling With The Chrome Developer Tools
- Writing Fast, Memory-Efficient JavaScript
- Powerful Workflow Tips, Tools And Tricks For Web Designers
- The Future Of Frontend Build Tools