Learn how to optimize your Backbone.js view for better performance
In the first part of this series: Getting Started with Backbone.js Views – Part I : Introduction, we learned the basics of how to implement a view in Backbone.js. Although the process of extending the Backbone.View class was quite similar to extending the Backbone.Route constructor, our code was not efficient. There are two areas to address: 1) Multiple constructor instantiations, and 2) Separation of presentation and data.
Multiple constructor instantiations
Example # 1A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var AppRouter = Backbone.Router.extend({ routes: { "message/:text" : "message" } message: function (text) { //instantiate the MessageView class var view = new MessageView({el: $('#main')}); //pass some data to the view view.options.message = text ? text : 'No Message Provided!'; //render the view view.render(); } }); |
In Example # 1A, we have the “message” method that is meant to handle any request to the “#/message/:text” route. While this method works as intended, we instantiate the the “MessageView” class each and every time that route is requested. There is no reason to instantiate that class more than once, and from a performance standpoint, this code is inefficient.
Example # 1B
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var AppRouter = Backbone.Router.extend({ routes: { "message/:text" : "message" }, initialize: function () { //instantiate the MessageView class (this only happens once) this.view = new MessageView({el: $('#main')}); }, message: function (text) { //pass some data to the view this.view.options.message = text ? text : 'No Message Provided!'; //render the view this.view.render(); } }); |
In Example # 1B, we’ve fixed the instantiation problem by leveraging the “initialize” method. If you remember from the article: “Getting Started with Backbone.js Routes – Part IV: Configuring an Initialization Function”, the “initialize” method only executes once. This is a perfect place to handle setup tasks for views. In Example # 1B, we instantiate the “MessageView” constructor. But, instead of assigning the newly instantiated object to a variable, we assign it to “this”, which is the instance of the “AppRouter” constructor. The reason we do this is because we will need access to that instance object from the “message” method.
Then, in the “message” method, we reference that instance object twice: when setting its “options.message” property and then when calling its “render” method. Both of those actions happen every time the “#/message/:text” route is requested.
Example # 2A
1 2 3 4 5 |
template: function(){ return '<p class="messageView mainMessage">' + 'The message is: <em>' + this.options.message + '</em>' + '</p>'; } |
In Example # 2A, we can see the other problem with our code: we mix JavaScript in with our HTML. While this does work, but it is not the most efficient way to go, and we are mixing presentation with data. We will fix this by using Handlebars.js.
Example # 2B
1 2 3 4 5 |
template: Handlebars.compile( '<div class="messageView mainMessage">' + '<p><b>The Message Is: </b><em>{{message}}</em></p>' + '</div>' ) |
In Example # 2B, we have replaced our “template” method with a call to the Handlebars.compile method. We pass it our string of HTML with one small but important change: the double-curly-braces templating syntax: {{message}}. A discussion of JavaScript templating is beyond the scope of this article, but it is important to note that by using Mustache.js (or a similar templating library), we do not need to mix-in JavaScript with the HTML string we pass to the Handlebars.compile method. The double-curly-braces templating syntax: {{message}} safely retrieves the data that we reference.
Example # 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//extend the Backbone.View class var MessageView = Backbone.View.extend({ template: Handlebars.compile( '<div class="messageView mainMessage">' + '<p><b>The Message Is: </b><em>{{message}}</em></p>' + '</div>' ), render: function () { this.$el.html(this.template(this.options)); return this; } }); var AppRouter = Backbone.Router.extend({ routes: { "": "defaultRoute", "message/:text" : "message" }, defaultRoute: function () { //this markup will does not utilize a Backbone view $('#main').html('<div class="mainMessage">This is the default route</div>'); }, initialize: function () { this.view = new MessageView({el: $('#main')}); }, message: function (text) { //pass some data to the view this.view.options.message = text ? text : 'No Message Provided!'; //render the view this.view.render(); } }); var app = new AppRouter(); $(document).ready(function() { Backbone.history.start(); }); |
In Example # 3, we have the full code for our working example. Visually there is nothing going on in Example # 3 that you have not seen already in an earlier article. But if you look at the page source, you’ll see that there is a dramatic difference in how we go about instantiating the MessageView constructor, as well as how we reference the data in our view’s “template” method.
HERE IS THE JS-FIDDLE.NET LINK FOR EXAMPLE # 3: http://examples.kevinchisholm.com/backbone/views/basics/html/part-ii.html
How to Demo:
This example will not appear to work any differently from the previous article’s examples. The important thing to note is what is going on under the hood. Take a look at the JavaScript file for Example # 3, so you can see that we have put the techniques discussed into action.
http://examples.kevinchisholm.com/backbone/views/basics/js/part-ii.js
Summary
In this article we learned two important optimization techniques for Backbone.js views: instantiating the view constructor only once, and separating the presentation from logic. We discussed the router’s “initialize” method as the best place to instantiate our view constructor, as well as how to use the JavaScript “this” keyword to make that instance object available to other methods in the Router class. We also learned how to leverage Mustache.js for client-side templating.