3

Today I was playing around with Backbone and Javascript, and came upon an interesting problem. Consider the following:

var App = {};
App.model = Backbone.Model.Extend({/* Omitted for brevity */});
App.view = Backbone.View.Extend({/* Omitted for brevity */});
App.obj = new App.view();

At this point I wanted to refactor for readability and mantainability's sake:

var App = {
   model: Backbone.Model.Extend({}),
   view: Backbone.View.Extend({}),
   obj: new view()  // Here comes trouble
}

The snippet above is what I wanted to obtain, but obviously the initialization doesn't work:

  • view is not in scope
  • App is not yet initialized, so App.view isn't usable.
  • this refers to window, so this.view isn't usable.

At this point, with the help of this answer I concocted a solution:

var App = {
   model: Backbone.Model.Extend({}),
   view: Backbone.View.Extend({}),
   get obj() { delete this.obj; this.obj = new this.view(); return this.view; }
}

Using the getter I'm able to delay the creation of the object instance until used (thus after completing App initialization), and then I replace the getter from within with the object instance.

Everything works as expected, but since I'm not exactly fluent with modern JS I was wondering if there was a different or proper way to achieve what I wanted. I was also wondering if this sort of implementation could cause drawbacks or unexpected problems I'm not considering.

Varstahl
  • 575
  • 4
  • 14
  • Not sure I really like this way of expressing the composition root. Why not just `const app = new App(model, view);`? – plalx Dec 18 '18 at 22:06
  • I'm sorry but I'm not fully understanding your comment, could you please elaborate? Also, keep in mind that there are multiple models, collections and views within the same App obj, I've just omitted for brevity. But I'm interested in fully understanding what you meant. – Varstahl Dec 18 '18 at 22:18
  • 2
    *There is nothing wrong with the first version*. You can't reference a property of the object you're initializing during initialization. You could lift it out into a var like so: `const view = Backbone.View.Extend({}); const App = model: Backbone.Model.Extend({}), view, obj: new view()};` but I don't think that's any better than your first stab at it. – Jared Smith Dec 18 '18 at 22:37
  • 1
    Possible duplicate of [Self-references in object literals / initializers](https://stackoverflow.com/questions/4616202/self-references-in-object-literals-initializers) – Jared Smith Dec 18 '18 at 22:37
  • I know that the first version is perfectly fine, I was just playing around with the language on a small scale fun pet project. While I can perfectly understand that my solution above has no real world application usage, I was just trying to learn new stuff. Also, I linked the "Self-references in object literals" in my question, I don't really think it's a duplicate, I was more asking about the consequences of using a getter as an instantiator. – Varstahl Dec 18 '18 at 22:46
  • That getter is really horrible code and should not be used in production. – Bergi Dec 18 '18 at 22:57
  • I know, I was more interested in learning the limits and the technicalities of the language than real world scenarios. For every real world case I would use the `var App = {};`. I was just wondering if there were different initialization methods I failed to research, but judging by the general consensus I already found everything there was to find within this problem. – Varstahl Dec 18 '18 at 23:08
  • 1
    @Varstahl Well like I said it's much cleaner if you encapsulate the initialization process in a proper constructor and leave the configuration part to the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/). I wouldn't use an behavior less object literal like that. – plalx Dec 19 '18 at 02:30

1 Answers1

0

First and foremost I want to thank everyone for the insights. For the sake of completion, I wanted to post what seems to be the best and less obnoxious way to obtain what I wanted in the first place. While my hack works, it should never be used, and converting the literal into a constructor gives the same result with no hacks:

function App() {
   this.model: Backbone.Model.Extend({});
   this.view: Backbone.View.Extend({});
   this.obj = new this.view();
}
var app = new App();

This version keeps itself dry, and has two added benefits: instantiation, and "private" members. It could easily be rewritten as such:

function App() {
   var model: Backbone.Model.Extend({});
   var view: Backbone.View.Extend({});
   this.obj = new view();
}
var app = new App();

This would keep both model and view out of reach, while having easily accessible app.obj. Of course, if instantiation is an unwanted effect, nothing beats var App = {}.

Also to be noted that since my example was featuring Backbone, the constructor way is probably suggested when extensibility is welcome, and prototypes for the class can be added via _.extend(). This is the the way Backbone does it:

function App() {
    // […]
}
_.extend(App.prototype, {
    method1: function() {}
    method2: function() {}
});

References: _.extend(), object constructors, literals vs. constructors.

Varstahl
  • 575
  • 4
  • 14