Understanding _.each on backbone collections

18,697

Solution 1

_.each(list, iterator, [context]) Alias: forEach

Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is passed. Each invocation of iterator is called with three arguments: (element, index, list). If list is a JavaScript object, iterator's arguments will be (value, key, list). Delegates to the native forEach function if it exists.

underscore docs#each

The default context is the window object. By setting this to be the context, you are making this in the function map to this where the function was called.

See the following for reference on this topic:

Solution 2

To explain you need to first realize that in JavaScript only a function creates a new scope (that's right a loop doesn't actual create a new scope) and that unlike some other languages the context of that scope is mutable, meaning depending on how it's called the this within that scope might refer to different things.

As a result of this a common problem that arises is that you might have a inner and outer function and within the inner function you want to refer to the scope of the outer function but the inner function has changed the scope so that this no longer refers to the outer function.

In order to handle this we need to make sure that we save the context of the outer function (have a look at the following for a more detailed explanation).

A common pattern you might see in JavaScript is assigning the context (this) to a variable and then using that within the function.

For example your render function could technically have been rewritten as the following

 render: function () {
       var self = this; 
        _(this.collection.models).each(function(file) {
            var fileView = new FileView({ model: file});
            self.$el.append(fileView.el);            
        });
    }

Now that we provided a basic understanding of the this and context we can turn to the _.each function from Underscore.js, _.each like most of the functions in underscore.js take an optional third parameter which refers to the context which underscore.js then uses so that you can conveniently refer to that context.

Underscore.js also provides a utility function bind to bind a context to a function.

Share:
18,697
neuDev33
Author by

neuDev33

Updated on June 05, 2022

Comments

  • neuDev33
    neuDev33 almost 2 years

    Warning: I'm a backbone newbie.

    I'm iterating over all the models in my collection and rendering them. Simple enough, however, I wanted to make sure I understand well how this works. Here's what I have -

    Model:

    File = Backbone.Model.extend({});
    

    Collection

    Folder = Backbone.Collection.extend({ model: File });
    

    ModelView

    FileView = Backbone.View.extend({
        initialize: function() {
           _.bindAll(this, 'render');
           this.render();
        },
        render: function() {
            this.template = _.template(document.getElementById("fileTemplate").innerHTML);
            this.$el.html(this.template({ fileName: this.model.get("FileName") }));
        }   
    })
    

    CollectionView

    FolderView = Backbone.View.extend({    
        initialize: function () {
            _.bindAll(this, 'render');
            this.render();
        },
        render: function () {
            _(this.collection.models).each(function(file) {
                var fileView = new FileView({ model: file});
                this.$el.append(fileView.el);            
            },this); <--???
        }
    });
    

    This works perfectly fine. My question is about the _.each in my FolderView. Why do I have to pass the this back to my each loop? If I don't pass this, it refers to my window object instead of my collection. I know it's necessary to pass it, i just don't know why.

  • neuDev33
    neuDev33 over 10 years
    the 1st link "A bit about function contexts" was very useful. Thanks!
  • Isochronous
    Isochronous about 10 years
    EcmaScript 5 also provides a native bind method that doesn't require third-party libraries, assuming you're not targeting legacy browsers. jQuery also provides a $.proxy(fn, context) method that does the same thing. I've actually found that jQuery's proxy method is less brittle than the native implementation. For example, we had a backbone view that was extending a base view class, and we tried using doing events: { 'click #someId': aFnOnTheBaseClass.bind(this) } with no luck. Switched it to $.proxy(aFnOnTheBaseClass, this) instead and it worked great.
  • Jack
    Jack about 10 years
    @Isochronous The reason I mentioned underscore's .bind is because the question is specifically about underscore.js and what's the use of passing in this to it's each method.
  • Isochronous
    Isochronous about 10 years
    Yeah, I got that, I just wanted to make sure he was aware that underscore isn't required to achieve the desired functionality.