Today while writing some JavaScript code I ran into an interesting gotcha I wanted to briefly mention. It has to do with method binding; more specifically, the behavior I'm used to from Python. Here's a code sample:

class MyKlass:
    def __init__(self):
        self.num = 2

        f = self.foo
        self.result = f(10)

    def foo(self, othernum):
        return self.num + othernum

mk = MyKlass()
print(mk.result)

The interesting part is invoking self.foo indirectly via f(). This works and prints 12 as expected. It works because the assignment f = self.foo assigns a bound method to f. By "bound", I mean that the method knows which object it belongs to, and when its body is executed self is set to the right thing.

The same does not hold in JavaScript. Here's a straightforward translation:

'use strict';

var MyKlass = function() {
  this.num = 2;

  var f = this.foo;
  this.result = f(10);
}

MyKlass.prototype.foo = function(othernum) {
  return this.num + othernum;
}

var mk = new MyKlass();
console.log(mk);

This doesn't work. I get an error from Node.js:

  return this.num + othernum;
             ^
TypeError: Cannot read property 'num' of undefined

So what's going on? The reason is that, unlike in Python, the assignment f = this.foo does not assign a bound method to f. It's simply a property lookup on the object this; it indeed has the property foo, which is a function. f now refers to that function. That's it. There is no implicit binding going on.

Once the problem is understood, it's easy to solve. The most direct solution is to bind the method explicitly on assignment, using Function.prototype.bind:

var bound_f = this.foo.bind(this);
this.result = bound_f(10);

An alternative solution is not to bind foo when a new reference to it is made, but do it when it's invoked through that reference. This can be done using Function.prototype.call (or equivalently using its cousin apply):

var f = this.foo;
this.result = f.call(this, 20);

This difference between the two languages exists because unlike Python, JavaScript does not have a first-class entity for a bound method. This is not surprising, because the Python method is bound to an instance, but instances in the "classical" sense don't exist in JavaScript (see how the class and method concepts from "classical OO" are simulated above with JS's constructor functions and prototypal inheritance).

What does exist in JavaScript is lexical binding and closures. While the bind method is new in ES5, its effect can be simulated very easily:

Function.prototype.bind = function(obj) {
  var self = this;
  return function () {
    return self.apply(obj, arguments);
  };
}

Note that the actual implementation of bind would be just a little more complicated because bind also supports partial application of arguments (some of the arguments can be provided during binding, and the rest on invocation).


Comments

comments powered by Disqus