Pythonic JavaScript Methods
What do I mean by "Pythonic" methods?
For anyone who hasn't used Python before, every instance method in Python takes the object it is bound to as its first argument, which is named self by convention.
class Foo(object):
def bar(self):
print "This is me:", self
When I refer to "Pythonic" methods, I simply mean methods on an object which have their first argument bound to that object.
Why would we want to make any of our JavaScript methods "Pythonic"?
This is a very good question, and if I did not have an answer this whole blog post would be questionable. Some people like the way methods work in Python, others do not. I do not want to discuss whether it is good for Python; I want to explain why it is good for JavaScript. Funny enough, the arguments behind whether it is good for Python and whether it is good for JavaScript don't really intersect. This is due to the nature of this in JavaScript, and how it differs from self in Python.
Unlike Python's self, JavaScript's this is not just a normal variable or parameter. It has dynamic binding (as opposed to lexical) meaning it is a reflection of the context of the surrounding function when it is invoked rather than when it is defined. It is common that the value of this will change in the middle of your method definitions:
var myObject = {
foo: function () {
console.log(1, this);
(function () {
console.log(2, this);
}());
setTimeout(function () {
console.log(3, this);
}, 100);
}
};
myObject.foo();
// Logs:
// 1 myObject
// 2 window
// 3 window
If you wanted the second and third calls to console.log to have access to the same this that the first one does, the common solution is to create a normal variable that references the desired context:
var myObject = {
foo: function () {
var that = this;
console.log(1, that);
(function () {
console.log(2, that);
}());
setTimeout(function () {
console.log(3, that);
}, 100);
}
};
myObject.foo();
// Logs:
// 1 myObject
// 2 myObject
// 3 myObject
But I get tired of doing that all the time. Tired of remembering when I need to define that until after I have a bug. Tired of the ugly blemish it puts in my code. I would rather write this:
var myObject = bindSelf({
foo: function (self) {
console.log(1, self);
(function () {
console.log(2, self);
}());
setTimeout(function () {
console.log(3, self);
}, 100);
}
});
myObject.foo();
// Logs:
// 1 myObject
// 2 myObject
// 3 myObject
I find this syntactic helper especially nice when defining prototypes or extending existing objects. These situations tend to rely on state that is stored in this more often than other examples, so I find the utility added by binding this to self also tends to be greater.
var MyView = Backbone.View.extend(bindSelf({
events: {
"click :input": "clickHandler"
},
initialize: function (self, opts) {
...
},
render: function (self) {
...
},
clickHandler: function (self, event) {
...
}
}));
What is the implementation?
function bindSelf (obj) {
// 1
for (var k in obj) {
if (obj.hasOwnProperty(k) && typeof obj[k] === "function") {
// 2
(function (oldMethod) {
obj[k] = function () {
// 3
return oldMethod.apply(
this,
[this].concat(toArray(arguments))
);
};
}(obj[k]));
}
}
// 4
function toArray (thing) {
return Array.prototype.slice.call(thing);
}
return obj;
}
What is it doing? We are iterating through each of the methods on obj and rebinding the name of each method to a new function which calls the old method with its this concatenated with its arguments. That was a mouthful.
Here is the step-by-step:
First, we iterate over all of the keys in obj which are not from the prototype chain and whose values are functions. This is important. We obviously don't want to try and redefine non-method attributes of obj as functions, which is why we check that typeof obj[k] === "function". However, we also do not want to break any of the object's methods which are inherited from its prototype, which is why we check that obj.hasOwnProperty(k).
Before rebinding obj[k] to the new function, we need to make an immediately invoked function expression so that we create a new variable oldMethod on every iteration of the loop. If we didn't do this, and incorrectly wrote the code something like this:
... // At the top level of `bindSelf`. var oldMethod; ... // Inside the loop. oldMethod = obj[k]; obj[k] = function () { return oldMethod.apply( this, [this].concat(toArray(arguments)) ); } ... ...
then we would capture the same oldMethod variable in the closure of every new function. Unlike languages such as C, JavaScript does not create a new scope every time you open the curly braces. Every new method would call the same oldMethod; definitely not what we want.
Okay we are finally creating these new functions and rebinding them to where the oldMethod used to live. When we call oldMethod, lets make sure that this inside oldMethod is still bound to this; and lets also add this to the front of the arguments we were called with before passing them to oldMethod. This is what we set out to do all along. Its really quite simple from this point on, you just need to understand what Function.prototype.apply does.
The last piece is our good old friend toArray. Any JavaScripter worth their salt has probably written this exact function many times before, possibly with a different name. All it does is take an array-like object and turn it in to a real array. What is an array-like object? It is one which has a length property and possibly 0, 1, 2, etc. properties defined. The cannonical example is the arguments object, which is what we are using it for. See the problem with arguments is that it doesn't have any of the Array.prototype's methods attached, and that when you concatenate it with a list, you get unexpected results:
// Does *not* return [1,2,3,4,5,6] as you might expect! (function () { return [1,2,3].concat(arguments); }(4, 5, 6)); // Instead it returns something like this: [1, 2, 3, { 0: 4, 1: 5, 2: 6, length: 3 }]
By calling toArray on arguments before concatenation, we get the expected result.