Scott Weinstein
Scott Weinstein

Reputation: 19117

Why doesn't this CoffeeScript/JavaScript set the properties of created object?

I've the following two CoffeeScript class definitions. I expected them to the same behavior, but they don't. In particular accessing A on instances of DoesNotWork is undefined.

fields = ["A","B","C"]

class DoesNotWork
  constructor: () ->
    _.each(fields, (f) -> @[f] = ko.observable(''))

class DoesWork
  constructor: () ->
    @A = ko.observable('')
    @B = ko.observable('')
    @C = ko.observable('')

the above code compiles to

var DoesNotWork, DoesWork, fields;
fields = ["A", "B", "C"];
DoesNotWork = (function() {
  function DoesNotWork() {
    _.each(fields, function(f) {
      return this[f] = ko.observable('');
    });
  }
  return DoesNotWork;
})();
DoesWork = (function() {
  function DoesWork() {
    this.A = ko.observable('');
    this.B = ko.observable('');
    this.C = ko.observable('');
  }
  return DoesWork;
})();

What newbie JS subtly am I missing?

Upvotes: 3

Views: 808

Answers (3)

Trevor Burnham
Trevor Burnham

Reputation: 77416

Yet another solution (arguably the most readable and efficient) is to skip _.each and instead use CoffeeScript's for...in iteration:

for f in fields
  @[f] = ko.observable ''

You could even postfix the loop to make it a one-liner:

@[f] = ko.observable('') for f in fields

Remember that loops in CoffeeScript don't create context or affect scope; only functions do.

Upvotes: 6

Trevor Burnham
Trevor Burnham

Reputation: 77416

Craig's answer is correct, but an alternative solution is to define your anonymous function as a bound function. In this case, that would let you write

_.each(fields, ((f) => @[f] = ko.observable('')))

The => binds the function to the context in which it's defined, so that this always means the same thing in the function no matter how it's called. It's a very useful technique for callbacks, though in the case of _.each, it's a bit less efficient than passing this in.

You could do the same thing using Underscore by writing

callback = _.bind ((f) -> @[f] = ko.observable('')), this
_.each(fields, callback)

but => saves you a lot of typing!

Upvotes: 3

Craig
Craig

Reputation: 7671

'this' in the anonymous function passed to _.each is bound to the anonymous function, not the parent object. _.each does allow passing a context object so that this will be bound properly though

http://documentcloud.github.com/underscore/#each

so pass a ref to the object you are trying to bind to in the 3rd arg of each:

class ShouldWorkNow
  constructor: () ->
    _.each(fields, ((f) -> @[f] = ko.observable('')),this)

Upvotes: 4

Related Questions