0
var PlaylistView = Backbone.View.extend({
  el: '#expanded-container',

  initialize: function() {
    this.bg = chrome.extension.getBackgroundPage();
    this.$('.list-group').empty();
    var realThis = this;
    _.each(this.bg.Playlist.models, function (song) {
      // append to playlist, rendering song template?
      var songView = new SongView({ model: song });
      console.log(realThis); // THIS is what I want
      console.log(this) // this is NOT what I want
      //this.$el.append(songView.render().el); // hence, this does NOT work
      realThis.$el.append(songView.render().el); // and THIS works
    });
  }
});

In the above code, this inside _.each() function points at the global window object because _.each() is invoked by the window. However, I still want this to point at PlaylistView. I have faced many similar situations and I often defined a variable that stored the initial value of this, just like realThis variable in the provided example. Is there any other conventional way to deal with this?

Note: I am following this book to learn Backbone, and it shows the following code as example.

var ListView = Backbone.View.extend({
  render: function(){

    // Assume our model exposes the items we will
    // display in our list
    var items = this.model.get('items');

    // Loop through each of our items using the Underscore
    // _.each iterator
    _.each(items, function(item){

      // Create a new instance of the ItemView, passing 
      // it a specific model item
      var itemView = new ItemView({ model: item });
      // The itemView's DOM element is appended after it
      // has been rendered. Here, the 'return this' is helpful
      // as the itemView renders its model. Later, we ask for 
      // its output ("el")
      this.$el.append( itemView.render().el ); // <--- *THIS IS WRONG?
    }, this);
  }
});

In this case, wouldn't this inside _.each loop point at the wrong object, just like in my code? Is this an error in the book or am I misunderstanding something? Thank you!

Reference: Learning this keyword

Maximus S
  • 10,759
  • 19
  • 75
  • 154
  • possible duplicate of [How to access the correct \`this\` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) – Felix Kling Apr 17 '14 at 01:51
  • 1
    The difference between your two code pieces it that in the second one you pass also `this` as third argument to `_.each`. See the documentation: http://underscorejs.org/#each, *"`_.each(list, iterator, [context])` 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."* This is also explained in section "Set this of the callback - part 2" in my answer linked to in my previous comment. – Felix Kling Apr 17 '14 at 01:53
  • *"In the above code, `this` inside `_.each()` function points at the global `window` object because `_.each()` is invoked by the `window`."* That's actually not correct. `_.each` is executed by the `initialize` method. `this` refers to `window` because `_.each` executes the callback in a way that sets `this` to `window`. – Felix Kling Apr 17 '14 at 01:57

2 Answers2

2

From http://underscorejs.org/#each:

each _.each(list, iterator, [context])

The iterator is bound to the context object, if one is passed.

Within initialize(), this points to your Backbone View. If you pass this as the 3rd argument to _.each() then this will refer to your Backbone View within the iterator function.

I have faced many similar situations and I often defined a variable that stored the initial value of this, just like realThis variable in the provided example. Is there any other conventional way to deal with this?

Yes. If you are in an ES5 (non-IE8) environment, use Function.prototype.bind(). For backwards compatibility, use _.bind().

var func = function (greeting) {
  return greeting + ': ' + this.name;
};

if (usingUnderscore) {
  func = _.bind(func, {name: 'moe'}, 'hi');
} else if (es5environmentHooray) {
  func = func.bind({name: 'moe'}, 'hi');
}

func();
=> 'hi: moe'
Jackson
  • 9,188
  • 6
  • 52
  • 77
1

You can change this of a particular function by using .bind():

function foo() {
    alert(this.x);
}
var boundFoo = foo.bind({x: "bar"});
boundFoo();

This alerts "bar".

Saving the outer this as realThis or that is also common if you want access to both the inner and outer this.

Underscore uses .call() to change this of the iterator function you pass it. Specifically, _.each() has a third parameter that allows you to specify what you want this to be, so that example is correct. See the underscore.js source.

univerio
  • 19,548
  • 3
  • 66
  • 68
  • Hi, thank you for your answer. What's the difference between my code and the example code? How come `this` in example code should point at `ListView` while `this` in my code points at `window` ? – Maximus S Apr 17 '14 at 01:44
  • @MaximusS Because of the way underscore works. Underscore changes `this` of your function by using `.call()`. The difference is in the third parameter of `_.each()`. That specifies what the context (a.k.a. `this`) should be. – univerio Apr 17 '14 at 01:57
  • I missed the third parameter! – Maximus S Apr 17 '14 at 02:33