Reputation: 179
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
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.FeedItem
s, in your case.
Upvotes: 1
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 changeitems[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.
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