ferris wheel at a theme park

JavaScript Gotchas

By
Gideon Kreitzer
Published
2020/09/07

A compendium of gotchas that you might encounter while working with JavaScript.

1. Event handlers and method contexts

Let's suppose we're building a “Mailbox” class that will provide the service of sending form data to a mail server. This class has an event handler method on its prototype. Inside the constructor, we attach an event to this method as one of the bootstrapping steps.

We'd like to use the validate method inside our send method by referencing it with this.validate(), thinking that it will find it on the prototype chain of our instance. However, addEventListener will redefine the context of the send method during execution by assigning its this reference to the DOM node to which the event was attached. In this case this will end up referencing the browser's form object which does not have our validate method on its prototype chain.

While we'd otherwise expect this behaviour from addEventListener, it can be overlooked when your mind is deep into the weeds of creating a new class and naturally assumes that since this is being used inside a method that is explicitly notated on the Mailbox class it should also continue to be bound to it.

The following snippet shows the relevant code excerpts from our hypothetical class.

// our example class
function Mailbox(options) {
    this.form = options.formElement || 'form';
    this.method = options.httpMethod || 'POST';
    //...
    
    // attach submission event to the "send" handler
    this.form.addEventListener('submit', this.send);
}

// a basic field validation method that
// we intend to use in the send() handler
Mailbox.prototype.validate = function() {
    // the validation code
}

// the event handler
Mailbox.prototype.send = function(event) {
    // Normally this method of referencing a method
    // on the same class will work, but since send()
    // had its context changed by addEventListener()
    // it has lost access to Mailbox's prototype and 
    // will throw an error.
    this.validate();
    //...
}

We can avoid the error by explicitly referencing the validate() method with Mailbox.prototype.validate(). This will work, but you'll have to do this for every other property of this class that you would like to access within the send method.

Mailbox.prototype.send = function(event) {
    // one solution is to use an explicit reference
    Mailbox.prototype.validate();
    //...
}

A better solution would be to keep the context of the class consistent by locking in this when executing send.

function Mailbox(options) {
    // persist the desired (class) context by using .bind()
    this.form.addEventListener('submit', this.send.bind(this));
}

Mailbox.prototype.send = function(event) {
    // works again!
    this.validate();
    //...
}
Credits
Eugene Chystiakov
Tags
gotchas bugs JavaScript