Written by: steve ross on July 29th 2011

Coffeescript, if you haven't heard, is a "little language that compiles into JavaScript". It has a small number of very useful patterns added right into it. One of the is class and the other is a binding operator, =>. Let's talk a bit about binding.

This is not an issue unique to Coffeescript -- it's present in Javascript and there are all kinds of things built into frameworks like Prototype that help work around the "problem". Basically, the question is "what does this refer to?" If you've never butted heads with the problem, you might be why someone would not be able to know such a simple thing at the time they created their code. But because of how scopes are created in Javascript, and hence also in Coffeescript, it is easy to think you are working with one this when in fact you're not. An example is worth a thousand words:

obfuscateEmail = (s) ->
  s.replace(/(\S+)@(\S+)/, (str, user, domain) ->
    "#{user}@#{domain}"
  )

console.log obfuscateEmail('me@mydomain.com')

There's nothing too special here. If you have Coffeescript installed, just save this to obfuscate.coffee and run it by typing coffee obfuscate.coffee. The result is, as expected, me@mydomain.com.

But there are no references to this here. Let's expand on this just a bit:

class PersonalData
  constructor: (@email) ->

  obfuscatedEmail: () ->
    @email.replace(/(.+?)@(.+?)/, (str, user, domain) ->
      "#{@email} maps to: #{user}@#{domain}"
    )

p = new PersonalData('me@mydomain.com')
console.log p.obfuscatedEmail()

Here I've added a nice Coffeescript addition, class to encapsulate things -- presumably PersonalData has more stuff than just an email, but in this case, I'm just specifying a constructor with one argument, email. I've used the same method I showed above, except I renamed it obfuscatedEmail and added a little tag to the front that gives me a before and after look. The result is, counter-intuitively, undefined maps to: me@mydomain.com.

And that's how we reach the binding issue. What has happened here is that @email, the instance variable bound to the this assigned to p is hidden when the function to do the replacement is defined because

There are plenty of workarounds, but Coffeescript provides a nifty operator to address this exact issue, the => or binding operator. So check it out. The exact same code, with one change:

class PersonalData
  constructor: (@email) ->

  obfuscatedEmail: () ->
    @email.replace(/(.+?)@(.+?)/, (str, user, domain) =>  # Used the binding operator
      "#{@email} maps to: #{user}@#{domain}"
    )

p = new PersonalData('me@mydomain.com')
console.log p.obfuscatedEmail()

And now @email refers to the email bound to the enclosing scope. It's like magic!