How to improve rendering performance when using $.html()
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.
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);
}
}