Question

I am programming with Backbone.js in CoffeeScript and have a problem with inheritance in relation with lodash and the merge function.

There is a superclass

class NavigationView extends Backbone.View
  config:
    test:
      string: "Test"

and two classes derived from it

class SubView extends NavigationView

  initialize: ->
      # Setting the view's template property using the Underscore template method
    _.merge @config, {
        test:
          string: "bla"
      }

class IndexView extends NavigationView
...

If I change within SubView's function initialize the config variable it is also changed in an instance of IndexView.

I instantiate my objects like so, within a BackBone.Router class:

index: () ->
    # Instantiates a new view which will render the header text to the page
    new IndexView()

  sub: () ->
    new SubView()

I created a fiddle to show it: http://jsfiddle.net/hijolan/9VeND/

Any ideas how to do that?

Best regards, hijolan

Was it helpful?

Solution

Your problem is that _.merge modifies its first argument in-place:

_.merge(object [, source1, source2, …])

Merges enumerable properties of the source object(s) into the destination object.

Note that where the documentation says destination it really means object. The intent of merge is to be a deep version of _.extend and the Underscore docs are explicit about what happens:

extend _.extend(destination, *sources)

Copy all of the properties in the source objects over to the destination object, and return the destination object.

You'll notice that lodash's extend (AKA assign) also gets the parameter names mixed up:

_.assign(object [, source1, source2, …])

Assigns own enumerable properties of source object(s) to the destination object.

Again they mean object when they say destination.

When you do this:

class NavigationView extends Backbone.View
  config:
    test:
      string: "Test"

The config ends up attached to the prototype for NavigationView and so the exact same config object will be seen by NavigationView and its subclasses. That means that @config is the prototype's config in your initialize:

_.merge @config, { test: { string1: "blub" } }

so _.merge will merge the new values right into the prototype's config and that makes the change visible all the way up to NavigationView and down into all of its subclasses. If you trace the inheritance back up, you'll find that @config in that context is from NavigationView so your _.merge is just a tricky and confusing way of writing:

_.merge NavigationView::config, ...

The root of the problem is that _.extend and _.merge modify their first argument. The way out of this trap is to supply a destination object that is safely writeable:

@config = _.merge { }, @config, { test: { string1: 'blub' } }
# ----------------^^^

Demo: http://jsfiddle.net/ambiguous/7j2FM/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top