Skip to content

Latest commit

 

History

History
357 lines (255 loc) · 18 KB

gotchas.md

File metadata and controls

357 lines (255 loc) · 18 KB

Common JavaScript "Gotchas"

Introduction

JavaScript has a lot of weird behaviours that trip up noobs to the language - especially those acquainted with more traditional OOP languages. Hopefully this guide will provide a quickly scannable, easily understood list to save a lot of pain to those getting acquainted with the language.

The intended audience for this article are engineers from other programming languages who are working with JavaScript for the first time. This article will not focus on detailed explanations of why the language works the way it does. It's merely intended to get you past the early hurdles quickly.

JavaScript is a flexible language with many ways to achieve the same result. What I document below is not the single "best" way. Rather, it's a series of strategies I use to write code in such a way that it is less confusing to people new to the language.

If you take nothing else away from this article, I highly recommend you read JavaScript: The Good Parts, by Douglas Crockford. It's the single best resource I'm aware of for getting new JavaScript developers up to speed.

Note that I use Allman indentation style in this document, despite the fact that a large portion of the JavaScript community frowns upon it (and for reasons I don't completely disagree with). However, I find this indention style to be much clearer and more readable than the alternatives. I like its gestalt. But please don't take my use of this indentation style as a suggestion that all JavaScript should be written using Allman style.

Author:
Steve Kwan
mail@stevekwan.com
http://www.stevekwan.com/

Originally from my GitHub:
https://github.com/stevekwan/best-practices/

Common Javascript "Gotchas"

Let's talk about some of the JavaScript 101 problems that noobs to the language encounter. If you're relatively unfamiliar with JavaScript, I highly recommend you read this section - it'll save you a lot of pain down the road.

The var keyword: what exactly does it do?

var declares a variable. But...you don't need it. Both these will work:

var myFunction = function()
{  
    var foo = 'Hello'; // Declares foo, scoped to myFunction
    bar = 'Hello';     // Declares bar...in global scope?
};

But you should never, EVER use the latter. See the comment for the reason why.

If you ever forget the var keyword, your variable will be declared...but it will be scoped globally (to the window object), rather than to the function it was declared in.

This is extremely bizarre behaviour, and really an unfortunate design decision in the JavaScript language. There are no pragmatic situations where you would want local variables to be declared globally. So remember to always use var in your declarations.

Why are there so many different ways to define a function? What are the differences?

In the wild, you'll most commonly see three types of function definitions:

myFunction1 = function(arg1, arg2) {};     // NEVER do this!
function myFunction2(arg1, arg2) {};       // This is OK, but...
var myFunction3 = function(arg1, arg2) {}; // This is best!

Assigning the function to a variable via the = operator is called a function expression. This is what happens to myFunction1 and myFunction3. The syntax used with myFunction2 is called function declaration.

Of the three options, the first option is bad because it assigns the function to a variable without the var keyword. This creates a global variable!

The second option is better, and is properly scoped, but it leads to a slightly more complicated syntax once you get into closures. It can also cause your code to behave in ways you may not expect due to JavaScript variable hoisting.

The third option is best, is syntactically consistent with the rest of your variables, and doesn't throw the function into global scope.

There are other ways to define a function which can be useful for debugging. See Appendix A for details.

The this keyword: how does it behave?

this in JavaScript does not behave the way you would expect. Its behaviour is very, very different from other languages.

If you are merely looking for a way to encapsulate your code or create a Singleton, you may be better off using the Module Pattern (here's an example) than something relying on this. But if you truly need to use this to write object-oriented JavaScript, here's an explanation...

In more sane languages, this gets you a pointer to the current object. But in JavaScript, it means something quite different: it gets you a pointer to the calling context of the given function.

This will make much more sense with an example:

var myFunction = function()
{
    console.log(this);
};
var someObject = {};                // Create an empty object.  Same as: new Object();
someObject.myFunction = myFunction; // Give someObject a property
someObject.myFunction();            // Logs Object
myFunction();                       // Logs...Window?

That's bizarre, isn't it? Depending on how you call a function, its this pointer changes.

In the first example, because myFunction was a property of someObject, the this pointer referred to someObject.

But in the second example, because myFunction wasn't a property of anything, the this pointer defaults to the "global" object, which is window.

The important take-away here is that this points to whatever is "left of the dot."

Note that you can actually override what this points to by using the built-in call() and apply() functions.

As you can imagine, this causes a ton of confusion - particularly for new JavaScript developers. My recommendation is to avoid writing code in such a way that relies on the intricacies of this.

WTF are constructor and prototype?

If you're asking this question, it means you're getting knee-deep into JavaScript OOP. Good for you!

The first thing you need to know is that JavaScript does NOT use classical OOP. It uses something called prototypal OOP. This is very, very different. If you really want to know how JavaScript OOP works, you need to read Constructors considered mildly confusing, by Joost Diepenmaat. Joost does a better job of explaining it than I ever will.

But for the lazy, I'll summarize: JavaScript does not have any classes. You don't create a class and spawn new objects off of it like in other languages. Instead, you create a new object, and set its "prototype" as the old one.

When you refer to an object's property, if that property doesn't exist JavaScript will look at its prototype...and its prototype, and its prototype, all the way up until it hits the Object object.

So unlike the class/object model you see in other languages, JavaScript relies on a series of "parent pointers."

constructor is a pointer to the function that gets called when you use the new keyword.

This is best explained with an in-depth code example, so read this constructor vs prototype experiment if you want to learn specifically how constructor and prototype play together.

WTF is __proto__, and how is it different from prototype?

If you've tried debugging your JavaScript in Chrome Inspector, you may have noticed a __proto__ property on all your objects. Try copying and pasting this code Chrome Inspector's console, and dig into the __proto__ property to see what I mean.

var ParentObject = function()
{
    // If ParentObject had constructor code, it would go here
};

ParentObject.prototype.parentProperty = 'foo';

// Again, note we're not using a classical OOP class/object relationship here.
// In JavaScript, objects just "point" to their parent via prototypes.
var ChildObject = new ParentObject();

ChildObject.childProperty = 'bar';

console.log("ChildObject:");
console.dir(ChildObject);  // Outputs a viewable object

console.log("ParentObject:");
console.dir(ParentObject);

For me, that outputs a ChildObject that looks like this:

ChildObject:
ParentObject
    childProperty: "bar"
    __proto__: Object
        constructor: function ()
        parentProperty: "foo"
        __proto__: Object

And a ParentObject that looks like this:

ParentObject:
function () { // If ParentObject had constructor code, it would go here }
arguments: null
caller: null
length: 0
name: ""
prototype: Object
    constructor: function ()
    parentProperty: "foo"
    __proto__: Object
__proto__: function Empty() {}

I highly recommend checking this out yourself in Chrome Inspector, as it's a great debugging tool.

Let's look at ChildObject first. You'll notice it contains childProperty, which is what we would expect. It also has a property called __proto__, which itself is an object. Among its contents, it contains parentProperty.

Knowing this, you could reasonably assume that __proto__ is the same as prototype. Unfortunately, that's incorrect. The difference between prototype and __proto__ is subtle, but it's very important.

If you look at the output for ParentObject, you'll see that it contains both __proto__ AND prototype, making the situation even more confusing. So what's going on?

Well, it turns out that every function gets an automatically-created property called prototype. That's right, every function gets this property, because any function can be used as a constructor. As we discussed earlier, JavaScript uses prototype properties like this to figure out your object inheritance chain.

When you create a new object using the syntax:

var ChildObject = new ParentObject();

JavaScript will look in ParentObject, find its prototype property, and copy it into ChildObject. But it will not copy it into ChildObject as prototype...it will copy it in as __proto__.

So why not copy it in as prototype? For a variety of reasons, the main one I am aware of being that it is totally possible for an object to have both a prototype and a __proto__. In fact, this is quite common, because all functions have a prototype and all objects have a __proto__. Heck, take a look at our ParentObject up above...it definitely does.

This can lead to an interesting scenario where you create a ChildObject, and then change the prototype property of the ParentObject later. If you do this, ChildObject will continue to use the old prototype.

Let's see an example:

var ParentObject = function()
{
    // If ParentObject had constructor code, it would go here
};

ParentObject.prototype.parentProperty = 'foo';

// Again, note we're not using a classical OOP class/object relationship here.
// In JavaScript, objects just "point" to their parent via the prototype
// property.
var ChildObject = new ParentObject();

ChildObject.childProperty = 'bar';

delete ParentObject.prototype;

console.log("ChildObject:");
console.dir(ChildObject);

Even though we have removed the prototype property from ParentObject, outputting ChildObject will continue to look like this:

ChildObject:
ParentObject
    childProperty: "bar"
    __proto__: Object
        constructor: function ()
        parentProperty: "foo"
        __proto__: Object

That's right, it will still have its parent pointer.

So to recap: prototype goes on constructors that create objects, and __proto__ goes on objects being created.

One more thing: for the love of beer and pork tacos, please don't ever try to manipulate the __proto__ pointer. JavaScript is not supposed to support editing of __proto__ as it is an internal property. Some browsers will let you do it, but it's a bad idea to rely on this functionality. If you need to manipulate the prototype chain you're better off using hasOwnProperty() instead.

WTF is a closure?

Closures are a concept that appear in functional languages like JavaScript, but they have started to trickle their way into other languages like PHP and C#. For those unfamiliar, "closure" is a language feature that ensures variables never get destroyed if they are still required.

This explanation is not particularly meaningful in and of itself, so here's an example:

// When someone clicks a button, show a message.
var setup = function()
{
    var clickMessage = "Hi there!";
    $('button').click
    (
        function()
        {
            window.alert(clickMessage);
        }
    );
};
setup();

In a language without closures, you'd likely get an error when that click handler fires because clickMessage will be undefined. It'll have fallen out of scope long ago.

But in a language with closures (like JavaScript), the engine knows that clickMessage is still required, so it keeps it around. When a user clicks a button, they'll see, "Hi there!"

As you can imagine, closures are particularly useful when dealing with event handling, because an event handler often gets executed long after the calling function falls out of scope.

Because of closures, we can go one step further and do something cool like this!

(function()
{
    var clickMessage = "Hi there!";
    $('button').click
    (
        function()
        {
            window.alert(clickMessage);
        }
    );
})();

In the above example, we don't even need to give the function a name! Instead, we execute it once with the () at the end, and forget about it. Nobody can ever reference the function again, but it still exists. And if someone clicks that button, it will still work!

Why is parseInt() mucking up when I get into big numbers?

Despite looking like it, JavaScript doesn't actually have an integer data type - it only has a floating point type. This isn't an issue when you do:

parseInt("1000000000000000", 10) < parseInt("1000000000000001", 10); //true

but add one more zero:

parseInt("10000000000000000", 10) < parseInt("10000000000000001", 10); //false

And you'll see where the difference between integers and floating points manifests.

Why does JavaScript have so many different ways to do the same thing?

So you can choose which way is best for you. Some parts of JavaScript are not designed that well. Your best guide to muddle through it is to read JavaScript: The Good Parts, by Douglas Crockford. He clearly outlines which pieces of the language you should ignore.

Appendix A: Other ways to define a function

As discussed earlier, there are many ways to define a function in JavaScript. In addition to the syntaxes described earlier, you can use:

var myFunction4 = function myFunction4(arg1, arg2) {};

Although more verbose, this option can be useful because it provides a little more context when debugging. This is a named function expression, which gives the function a name property. That property shows up when debugging.

But be aware of which name is used:

var myFunction5 = function aDifferentName(arg1, arg2) {};

console.log(myFunction5.name); // logs "aDifferentName"

Javascript minifiers such as YUI Compressor and UglifyJS often rename your functions, which can reduce the usefulness of this technique.

Acknowledgements

Thanks so much to the excellent Hacker News and Reddit communities for their input! In particular, thanks to:

Pier Paolo Ramon
Oliver Mader
Calvin Metcalf
Thomas Ballinger
Edwin Martin