1

I am working on a Backbone demo app that shows a list of tweets. As I am replacing all the "tweets" with different data I clear the list using $.html()

render: function() {
    $("#item-table").html('');
    this.collection.each(this.addItem);
}

I was wondering if anyone could give me a hint with what can I replace this $.html() for better performance, because by using $.html() I am causing reflows and which gives bad layout process times.

There are two other places in the code where I use $.html() and it would be really great if someone could give me advice on how to change those too if those other places are even possible.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
GeForce RTX 4090
  • 3,091
  • 11
  • 32
  • 58

1 Answers1

3

Create a new DocumentFragment to pre-render all the items, then update the DOM once.

Also, favor this.$(...) over the global jQuery selector $(...).

this.$ is a proxy to this.$el.find(...) which is more efficient, and less prone to select something outside of the view.

Using jQuery's core function ($()) inside a view can fail if the view wasn't rendered yet. So it's better to always manipulate through this.$el so you can make changes even before the view is actually put in the DOM.

Keep all the sub views created in an array to cleanly remove them later.

initialize: function() {
    this.childViews = [];
},
render: function() {
    // cache the list jQuery object
    this.$list = this.$("#item-table");

    // Make sure to destroy every child view explicitely 
    // to avoid memory leaks
    this.cleanup();

    this.renderCollection();
    return this;
},

The real optimization starts here, with a temporary container.

renderCollection: function() {
    var container = document.createDocumentFragment();

    this.collection.each(function(model) {
        // this appends to a in memory document
        container.appendChild(this.renderItem(model, false).el);
    }, this);

    // Update the DOM only once every child view was rendered.
    this.$list.html(container);
    return this;
},

Our renderItem function can still be used to render a single item view and immediatly put it in the DOM. But it also provides an option to postpone the DOM manipulation and it just returns the view.

renderItem: function(model, render) {
    var view = new Item({ model: model });

    this.childViews.push(view);
    view.render();
    if (render !== false) this.$list.append(view.el);
    return view;
},

To avoid memory leaks with dangling listeners, it's important to call remove on each view before forgetting about it.

I use an additional optimization by deferring the actual call to remove so we don't waste time now while the user waits.

cleanup: function() {
    var _childViewsDump = [].concat(this.childViews);
    this.childViews = [];

    while (_childViewsDump.length > 0) {
        var currentView = _childViewsDump.shift();
        // defer the removal as it's less important for now than rendering.
        _.defer(currentView.remove.bind(currentView), options);
    }
}
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • Thank you! I will do a benchmark for this app compared to React(virtual DOM) and I'm using this Backbone app as an example of KVO(key-value observation) view-model syncing mechanism. I just want to be sure - if I implement these optimizations will this app still be a good example for KVO view-model syncing mechanism? – GeForce RTX 4090 May 17 '17 at 21:51
  • @gfels I'm sorry, I'm not familiar with KVO view-model syncing mechanism. As for comparing Backbone rendering with React, it's irrelevant since Backbone doesn't provide rendering, it's all left to the developper to use whatever he likes. Backbone only offers jQuery by default, but still lets the dev to implement the app rendering. – Emile Bergeron May 17 '17 at 21:54
  • @gfels If, by _KVO view-model syncing mechanism_, you mean two-way data binding, then Backbone also doesn't provide that and there are libs that are specialized with this that can easily be used with Backbone. (Epoxy, Knockout, Backbone.Stickit, even React could be used) – Emile Bergeron May 17 '17 at 22:00
  • 1
    @gfels Just to add my two cents, if you're trying to implement 2way binding with backbone, also see [Rivets.js](http://rivetsjs.com/) and [SkateJS](https://skatejs.gitbooks.io/skatejs/content/) – T J May 18 '17 at 09:08