3

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?

1
  • 1
    It does work... it just doesn't do what you wanted it to do. Commented Mar 29, 2011 at 18:12

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

2 Comments

both answers are great choices. A shame there isn't a collection.each built into coffeescript, much like in Ruby. underscore does a fine job of patching this style but coffeescript comprehensions are pretty slick. I guess I prefer the loop mechanic up front as opposed to trailing (old c habits)
Right, CoffeeScript doesn't modify prototypes. You could use CoffeeScript in conjunction with Prototype.js if you want an each method on your arrays.
4

'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)

4 Comments

The function should be in (). It should be: _.each(fields, ((f) -> @[f] = ko.observable('')), this)
@Rocket: Thanks, I just copied the code in the example. Fixed.
One small correction: this isn't bound to the anonymous function by default, but rather to the global context, as when you write func.call null, ....
You really rarely need _.each in Coffee Script. I highly recommend for/in instead.
3

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.