Backbone Views and the Law of Demeter
a.doSomething() is probably ok, but
a.b.doSomething() is using too much knowledge of
How Backbone Helps
Backbone Views encourage better patterns in a couple ways:
Scoped Event Binding By Default
Let’s look at a trivial Backbone View that represents a navigation bar.
This view binds to the existing element on the page with the id
nav - this element becomes the view’s root. All elements under it can be considered direct properties of the view, and only this view should directly manipulate them. The
events property in the view’s configuration options here binds click events on elements matching the selector
li.item to the view’s
navigate method. This is automatically scoped to only match elements that are descendents of the View’s root element. We’ll only ever match events from our private DOM elements, and other Backbone components on the page won’t match events from our elements.
You can get the same effect with something like
$('#nav li.item a').click( /* nav function */ ), but this requires more discipline and doesn’t have the same effect of keeping all of our component’s event concerns in one place. It also establishes a common pattern of event handling across all our page components: convention over configuration.
Setting up scoped event binding is great for maintaining our limited knowledge and encapsulation with regard to event handling, but once it’s time to actually modify those DOM elements, we need a way to do those operations with the same limitations.
Backbone provides a convenient way to make a query scoped to just the view’s elements: the
this.$ function. Nothing fancy going on here: in a Backbone View, the root element of the View’s DOM is bound to
this.$ is just sugared-up
$(this.el).find. Let’s take our navigation bar example from above again. We’ll make it set a current location field when you click on a navigation link.
Nothing much surprising going on here: we grab the text from the link and plunk it down inside the element with class
location. But consider what we got by using the view’s scoped selector instead of a document-wide query:
We know we’re only modifying the DOM elements this view directly owns.
Our event handling code here doesn’t have to care if there’s more than one instance of NavigationView on the page. Multiple instances require extra care without a wrapping view- you’d need to know whose location element to modify and how to pick the right one.
This built-in knowledge of the component’s root also eliminates fragile traversals from the event’s element to the piece you want to modify. No more temptation to do stuff like
$(e.target).prev().children('.location')in your event handlers, only to have them break when you alter your structure.
Even better, with this pattern, use of queries outside of
this.$can serve as a useful warning sign that you’re stepping outside the limits of knowledge that your component should have.
We’re Just Getting Started