< return home

B is for Bind

ABCs of JavaScript

This article was originally published on #dev-a-day.

Alright, honesty time - I thought I would make it through at least the first half of the alphabet before I ran out of immediately obvious topics to write about; turns out I only got through A before hitting a snag. What cool, interesting or complex topic can I write about for B? B...b...break? I want to save that for Loops. B...b...booleans? That's way too broad. B...b...bcrypt? That's a package, which I guess counts, but it's not really part of the JavaScript ABCs.

google-search

When all else fails, you know where to go. Thanks to our dear friend frantic Google searching, we have our topic for today - bind! I am not surprised that it ranks 2nd in my search suggestions - binding is one of those topics that can be totally ignored 99% of the time but will send you running in circles for hours that 1% of the time where it's crucial.

Binding

So, what is binding? What do you bind? To what? When do you need to bind? Great questions! Let's tackle them one at a time.

What is binding?

To understand binding, we must first take a step back and look at this. Every operation in JavaScript is executed in a context that gives it access to a handful of variables by default. For example, in a browser context (e.g. - running JS code from a <script> tag in a web page), the context includes some global variables like window, document, location, and so on. We refer to this as the default context - it's the context you get out of the box.

console.log(window); // Window {...}

How does JS know where to get window in the code block above? It checks if window exists on this! We could rewrite the code above using this explicitly like so:

console.log(this.window); // Window {...}

What if we run a function from within another context? When you define a function on an object, that object becomes the function's context, and thus this within that function's scope is no longer the default context:

const context = {
  foo: 'foo',
  print: function() {
    console.log(this.foo);
  },
};

context.print(); // foo

Arrow Function Context

Now is a good time to point out the elephant in the room: arrow functions. Arrow functions are generally used as a dropin replacement for the slightly more verbose function() { } syntax, but they work slightly differently under the hood. Arrow functions do not get a context, so calling this within an arrow function will refer to the default global context:

const context = {
  foo: 'foo',
  print: () => {
    console.log(this.foo);
  },
};

context.print(); // undefined

What do you bind?

When you want to assign a context other than the default context, use .bind()! If you have a top level function that refers to this, you can change that function's context using bind:

function doStuff() {
  console.log(this);
}

doStuff(); // Window {...}

const foo = { foo: 'foo' };

const doStuffWithFoo = doStuff.bind(foo);

doStuffWithFoo(); // { foo: 'foo' }

If you don't want to create a new function (like doStuffWithFoo above), you can call a function and provide the context as the first argument:

function doStuff() {
  console.log(this);
}

doStuff(); // Window {...}

const foo = { foo: 'foo' };

doStuff.call(foo); // { foo: 'foo' }

This works with functions that have arguments as well, each argument just gets shifted over by one:

function doMoreStuff(a, b, c) {
  console.log(this, a, b, c);
}

doStuff(1, 2, 3); // Window {...} 1 2 3

const foo = { foo: 'foo' };

doStuff.call(foo, 4, 5, 6); // { foo: 'foo' } 4 5 6

When should you bind?

Binding is generally one of those concepts that you can ignore until you find yourself in a situation where you need it. A common case for binding in modern development is class methods:

class MyClass {
  classVariable = 'foo';

  print() {
    console.log(this.classVariable);
  }
}

const c = new MyClass();
c.print(); // foo

const f = c.print;
f(); // undefined

We want to print out the class's instance variable classVariable, but got undefined! By default, class methods are not bound to the parent class context. When writing classes, we generally perform this binding in the constructor where we do have direct access to the class context:

class MyClass {
  classVariable = 'foo';

  constructor() {
    this.print = this.print.bind(this);
  }

  print() {
    console.log(this.classVariable);
  }
}

const c = new MyClass();
c.print(); // foo

const f = c.print;
f(); // foo

The Class Properties Proposal provides an idiomatic way to automatically bind class methods to their parent class context:

class MyClass {
  classVariable = 'foo';

  print = () => {
    console.log(this.classVariable);
  };
}

const c = new MyClass();
const f = c.print;
f(); // foo

Don't let the arrow function fool you - though arrow functions in the wild do not have a context, in this proposal they will be automatically assigned to the parent class context.

Conclusion

Unlike most articles I've written in the past, I don't encourage you to go out and find everywhere you can to stuff .bind() into your app code. You're likely not using it now and will likely be better off keeping it that way. Add it to your hidden debugging arsenal, and if you ever see any unexpected undefined values, consider your code's context as a possible source of errors.

ramblings by Aaron Ross, otherwise known as superhawk610
> ...
© 2023 all rights reserved
built with Gatsby