1

I'm struggling a lot with Javascript prototyping; I don't seem to understand when I should be declaring an attribute in the prototype vs instantiation. Particularly, I don't understand, why in my code attached (a basic exercise in building a little Router class to treat a document more like an app than a page), attributes set at instantiation are persisting and thus accumulating in what I intend to be separate objects entirely.

Mostly this is a learning exercise, but I've scaled back some of the original code to help with contextual obfuscation*.

Code is here: http://codepen.io/anon/pen/xGNmWM

Basically, each time you click a link, the data-route attribute on the element should be picked up an event listener, a new Route object should be instantiated (and passed information about the intended route); finally the Router class should actually "launch" the Route, ie. make an ajax request or do some in-document stuff, whatever.

Right now, the Route.url.url attribute should be, in my obviously wrong understanding, created anew each time and then informed by passed data. Somehow, this attribute is persisting and thus accumulating passed information from each click.

I truly don't understand why.

**I have not removed anything that would effect my question; really it could be trimmed back even more but I realize the integrity of the question relies on a reasonable facsimile of the original code.

Jonline
  • 1,677
  • 2
  • 22
  • 53
  • 1
    You really should try to reduce your code to a minimal example that can be posted here, and also remove any dependencies on external libraries. You can post runnable snippets here (icon second to the right of `{}`). – RobG Aug 18 '15 at 22:42
  • I strongly suggest you take a look at Kyle Simpson's OLOO pattern to clear up some of the confusions with trying to force JavaScript into being an OO language. http://stackoverflow.com/questions/29788181/kyle-simpsons-oloo-pattern-vs-prototype-design-pattern – Tad Donaghe Aug 18 '15 at 22:43
  • Yeah I would have thought so too, but have been criticized in the past for this and told it was back SO form to do so. But I certainly can... I'll post a second, stripped down codepen – Jonline Aug 18 '15 at 22:43
  • 1
    "*attributes set at instantiation are persisting*". They don't. However, your `.url` attribute *is not set* at instantiation, neither in your constructor nor in your `init` method. – Bergi Aug 18 '15 at 23:46

2 Answers2

1

You have two problems.

By Value vs By Reference

In Javascript primitive types, as numbers, booleans and strings, are passed to another functions or set to another variable by value. That means that its value is copyed (cloned).

The object types, as objects, arrays and functions, are passed to another functions or set to another variable by reference. That means that variables of this type are just references to a content to memory. Then when you set an object to a variable, just its memory address is being copied.

When you pass the "route_data" its reference is copied. Then the Route constructor is working on same variable hold by Router. If you clone your object before pass it the problem is solved.

...
var route_data = this.route_data[ route_name ];
route_data = $.extend(true, {}, route_data);  // Clone object using jQuery
var route = new Route( route_name, route_data, request_obj);
...

Prototype

The Javascript has a prototypal inheritance, that means that each object points to its prototype that is another object.

var obj = { name: 'John' };
console.log(obj.__proto__);

All objects root prototype is Javascript Object by default. As noted by example above.

function Person(name) {
    this.name = name;
}

Person.prototype = {
    getName: function() {
        return this.name;
    }
}

var obj = new Person('John');

console(obj.getName());
console(obj.__proto__);
console(obj.__proto__.__proto__);

When you use new a new empty object is created and binded as this to specified function. Additionally its object prototype will point to the object specified on prototype of called function.

On get operations the Javascript engine will search on entire prototype chain until specified field is found.

But on set operations if the specified field does not exist on current object, a new field will be created.

When you define the url field on Route this should be static, because when you try to change it a new field is created.

If you verify your Route instance you will note that you have created duplicated url fields. One on object itself and another on prototype.

Skarllot
  • 745
  • 6
  • 16
1

I would have really appreciated a minimal code example posted here on SO rather than codepen. It would have saved me some time reading your code (you're not paying me for this after all).

Anyway, this is problematic:

Route.prototype = {
    // etc..
    url : {url: false, type: "get", defer: false}
    // etc..
}

Basically, what you're doing is this:

var global_shared_object = {url: false, type: "get", defer: false};
Route.prototype.url = global_shared_object;

Do you see the problem? Now when you do:

var route1 = new Route();
var route2 = new Route();

Both the .url property of route1 and route2 point to the same object. So modifying route1.url.url will also modify route2.url.url.

It should be noted that route1.url and route2.url are different variables. Or rather different pointers. Modifying route1.url will not modify route2.url. However, the way they've been initialized makes them both point to the same object so modifying that object can be done from either pointer.

The key to making this work is to create a new object for .url every time you create a new object. That can be done either in the constructor or in your case your .init() method:

Route = function (name, route_data, request_obj) {
    this.url = {url: false, type: "get", defer: false};
    this.init(name, route_data, request_obj);
}

Implied Lesson:

The lesson to take from this is that the object literal syntax is actually an object constructor. Not just a syntax.

// this:
{};
// is the same as this:
new Object();

So every time you see an object literal, in your mind you should be thinking new Object() then see if it makes sense to call it just once or do you need a new instance of it every time.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • This is concise, coherent and precisely the information I sought. Regarding the code example, I *wholly* agree; I used to always trim my code back to the minimum needed to be demonstrative of the problem, but was on several occasions warned in comments that this was bad SO form as I should be "posting my original code so answers are assured to be pertinent". As a clearly established SO user, can you comment to this? – Jonline Aug 19 '15 at 13:56
  • 1
    @Jonline: The key is to trim it to a minimum **working** example. That is, if I copy/paste that code into a blank HTML file it would demonstrate your problem. Trimming it to the minimum area you *think* may contain the bug is bad form. – slebetman Aug 19 '15 at 14:11
  • Why this answer was accepted? This doesn't resolve the problem. – Skarllot Aug 19 '15 at 16:05
  • @slebetman: Not really. The `route_data` continues to be passed as reference and changed on `Router` and `Route` classes fields. If you test it, you will confirm it. – Skarllot Aug 19 '15 at 16:13
  • I'd marked this as correct because the explanation of the theory was great and moreover sufficient to help me see the problem in my actual context. Since my codepen was modified from the original context I didn't use @slebetman's code but rather implemented the "lesson" (which, really, was the fundamental understanding problem on my part). To be fair to visitors arriving, though, @Skarllot's answer is more thorough as indeed the call to `$.extend()` was needed to repair my posted code. Apologies for the drama. – Jonline Aug 26 '15 at 20:11