Logan
Logan

Reputation: 179

What's wrong with this Coffeescript?

I'm iterating over some api data and mapping the API's data to Coffeescript objects. What's puzzling me is why just part of that data disappears between these two console.log statements:

items = (for item in @apiFeedPage.items
         fi = new API.FeedItem(item, @apiFeedPage.instance_url)
         console.log fi.comments.comments
         window.API.itemHash[fi.id] = fi  #save for later usage
         fi )       
console.log items[0].comments.comments

In the above, the first console.log outputs the comments I expect: fi.comments.comments is equal to an array of Comment objects (Comments are a property on a FeedItem) In the second console.log statement, the comments objects are there but are not assigned - as if the Comment constructor was run on a API response with no comments.

The constructor looks like this:

class API.FeedItem extends API.FeedComponent 
  # instance vars
  comments: {}

  constructor: (data, instance_url) ->
    super(data, instance_url)
    @parent = new API.User( data.parent )
    @comments.comments = (new API.Comment(api_comment) for api_comment in data.comments.comments)   
    @comments.total = data.comments.total
    @comments.nextPageUrl = data.comments.nextPageUrl

And I've confirmed that inside the constructor, the @comments.comments is properly assigned, which is what you'd expect since the first console.log statement has the expected objects. The first block of code above sits in a Ajax callback function using a fat arrow, so my initial suspicion that this had to do with losing the context of "this" doesn't seem to apply, and since all the other expected data inside FeedItem is there...

Any ideas on why the items[0].comments.comments is equal to [] in the second statement?

Upvotes: 2

Views: 277

Answers (2)

Asherah
Asherah

Reputation: 19347

To add to @mu is too short's answer, in CoffeeScript:

class X
  y: -> console.log 'y!'
  z: 'z'

gets translated to:

var X;

X = (function() {

  function X() {}

  X.prototype.y = function() {
    return console.log('y!');
  };

  X.prototype.z = 'z';

  return X;

})();

The key takeaway is that items declared immediately within the body of a class statement are set on the class's prototype. Prototypes being shared among instances, this means there's only one function object (or one 'z' string) shared across all X instances—or one comments object among all API.FeedItems, in your case.

Upvotes: 1

mu is too short
mu is too short

Reputation: 434585

I would guess that the last data.comments.comments inside your API.FeedItem constructor is empty. That would cause @comments.comments to be []. But then you'd ask:

Why would the last data.comments.comments value change items[0]?

The answer is simple, this:

class API.FeedItem extends API.FeedComponent 
  # instance vars
  comments: {}

creates a single comments = { } object that is attached to the API.FeedItem prototype and thus is shared by all instances of API.FeedItem; in other words, the comment is a lie, that's not an instance variable.

Consider this simplified analogue to your situation:

class AFI
  comments: { }
  constructor: (n) ->
    @comments.comments = [ 1 .. n ]

fitems = [ 1 .. 4 ]
items = (new AFI(i) for i in fitems)
console.log item.comments for item in items

You're probably expecting to see [1], [1,2], [1,2,3], [1,2,3,4] come out but you'll see four identical [1,2,3,4]s instead: http://jsfiddle.net/ambiguous/hf9zL/

If you fix your class to initialize @comments per instance:

class AFI
  constructor: (n) ->
    @comments = { }
    @comments.comments = [ 1 .. n ]

Then you'll see the [1], [1,2], [1,2,3], [1,2,3,4] that you're expecting.

Demo: http://jsfiddle.net/ambiguous/qs9Zy/

You could even throw a

console.log items[0].comments.comments == items[1].comments.comments
console.log [1] == [1]

in to see that the arrays are, in fact, the same array; the extra [1] == [1] comparison is to prove to you that == is actually comparing the objects rather than their content (i.e. CoffeeScript's == is JavaScript's ===).

A rule of thumb: don't try to define instance variables in the class definition, define them in the constructor.

Upvotes: 3

Related Questions