Skip to content

Instantly share code, notes, and snippets.

@dtao
Created May 21, 2013 16:55
Show Gist options
  • Save dtao/5621367 to your computer and use it in GitHub Desktop.
Save dtao/5621367 to your computer and use it in GitHub Desktop.
How 'this' in JavaScript is like 'instance_eval' in Ruby
function Agent(name) {
this.name = name;
this.command = function(subordinate, description, instructions) {
console.log(this.name + " tells " + subordinate.name + ": '" + description + "'");
instructions.apply(subordinate);
};
this.getCoffee = function() {
var recipients = Array.prototype.slice.call(arguments);
var names = recipients.map(function(agent) { return agent.name; });
console.log(this.name + " gets coffee for " + names.join(" and "));
};
this.drinkCoffee = function() {
console.log(this.name + " drinks coffee.");
};
};
var barack = new Agent("Barack");
var joe = new Agent("Joe");
var dan = new Agent("Dan");
(function() {
// Here 'this' is Barack.
this.command(joe, "Tell Dan to get us some coffee.", function() {
// Here 'this' is Joe.
this.command(dan, "Get us some coffee, Dan.", function() {
// Here 'this' is Dan.
this.getCoffee(joe, barack);
});
// Joe again.
this.drinkCoffee();
});
// Back to Barck.
this.drinkCoffee();
}).apply(barack);
class Agent
attr_reader :name
def initialize(name)
@name = name
end
def command(subordinate, description, &instructions)
puts "#{self.name} tells #{subordinate.name}: '#{description}'"
subordinate.instance_eval(&instructions)
end
def get_coffee(*recipients)
puts "#{self.name} gets coffee for #{recipients.map(&:name).join(' and ')}."
end
def drink_coffee
puts "#{self.name} drinks coffee."
end
end
barack = Agent.new("Barack")
joe = Agent.new("Joe")
dan = Agent.new("Dan")
barack.instance_eval do
# Here 'self' is Barack.
self.command(joe, "Tell Dan to get us some coffee.") do
# Here 'self' is Joe.
self.command(dan, "Get us some coffee, Dan.") do
# Here 'self' is Dan.
self.get_coffee(joe, barack)
end
# Joe again.
self.drink_coffee()
end
# Back to Barack.
self.drink_coffee()
end
// We don't necessarily need to use the constructor function pattern in JavaScript
// to make sense of 'this'. Here's an alternate way of implementing almost exactly
// the same functionality as agent.js, but without making Agent a constructor.
function createAgent(name) {
return {
name: name,
command: function(subordinate, description, instructions) {
console.log(this.name + " tells " + subordinate.name + ": '" + description + "'");
instructions.apply(subordinate);
},
getCoffee: function() {
var recipients = Array.prototype.slice.call(arguments);
var names = recipients.map(function(agent) { return agent.name; });
console.log(this.name + " gets coffee for " + names.join(" and "));
},
drinkCoffee: function() {
console.log(this.name + " drinks coffee.");
}
};
};
var barack = createAgent("Barack");
var joe = createAgent("Joe");
var dan = createAgent("Dan");
(function() {
// Here 'this' is Barack.
this.command(joe, "Tell Dan to get us some coffee.", function() {
// Here 'this' is Joe.
this.command(dan, "Get us some coffee, Dan.", function() {
// Here 'this' is Dan.
this.getCoffee(joe, barack);
});
// Joe again.
this.drinkCoffee();
});
// Back to Barck.
this.drinkCoffee();
}).apply(barack);
@dtao
Copy link
Author

dtao commented May 21, 2013

If you run ruby agent.rb and node agent.js or node agent2.js, you should get identical output.

I wouldn't argue that the this keyword in JavaScript isn't confusing sometimes; but I don't think it's quite the anathema of a language feature it's often made out to be. If you understand instance_eval in Ruby, it isn't really so different.

My hypothesis is that the main reasons this in JavaScript is perceived as so much worse are:

  1. Many more developers coming from Java and C#, where this does not change meaning in either an anonymous type (Java) or a closure (C#), end up also reading/writing JavaScript than end up reading/writing Ruby.
  2. The prevalence of this in JavaScript is much greater than that of instance_eval in Ruby; so it's considered a more "core" part of the language as opposed to a magical but seldom-used feature.
  3. JavaScript has a greater focus on asynchronous functionality than other mainstream languages; and as such JavaScript code uses closures themselves much more frequently (hence has more this-related bugs) than code written in other languages.

All that said, it's very possible I have no idea what I'm talking about, and this is actually way more confusing than I realize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment