1

OK, I doubt this is a unique situation, so either someone has done this, or someone has decided this isn't possible, at least in the way I am asking.

I have 2 prototyped variables (functions), one is the parent, the other is a helper. What I'd like to do is reference the parent from within the helper such that each instance of each parent can utilize the helper while allowing the helper to essentially extend the parent. I'm using jQuery and javascript.

Here's a simplified example:

var Parent = function(identity) {
    this.identity = identity;
    this.group = $(this.identity);
    this.helper = new Helper(this);
    return this;
};
Parent.prototype = {
    _setup : function(dolisten) {
        dolisten = dolisten || false;
        var that = this;
        that.group.each(function(i) {
            if ($(this).data('key') !== 'undefined') {
                that.elems[i] = new Object();
                that.elems[i].key = parseInt($(this).data('key'));
                // build out rest of elems object
            }
        });
        if (dolisten) {
            this._listen();
        }
    },
    _listen : function() {
        var that = this
        $('body').on('click tap', that.identity, function() {
            that.helper._showbusy(this, true);
            // do some other stuff
        }
    }
};
var Helper = function(reference) {
    this.reference = reference;
};
Helper.prototype = {
    _showbusy : function(elem, isbusy) {
        console.log(this.reference);
        // do some stuff to elem
    },
};

This works but creates an essentially infinite reference to Parent from within its own .helper node. So, without actually passing the parent every single time (hoping to create a single point of reference just once, the above seems to cause a reference "loop"?), can this be done elegantly?

In the end, what I would like is a simple:

var obj = new Parent('.myClass');
console.log(obj);
// result : Parent { identity:'.myClass', group: [Object object], helper: {reference : *Parent without helper key included, which is where the looping seems to occur}, etc...

EDIT: For the time being, I'm resorting to passing in the specific keys of the parent that are needed by the helper. This prevents the helper from infinitely cascading into copies of the parent and itself, but a more elegant solution, where helper has access to the entire parent instead of just the specific keys passed, without cascading helper's reference to parent to become parent -> helper -> parent -> helper -> parent -> etc...

Lazerblade
  • 1,119
  • 7
  • 17
  • http://javascript.crockford.com/private.html have a read. Make use of "Privileged" object for your "Helper" – Mike Oct 01 '14 at 17:29
  • What do you want? `Parent` extand `Helper` or vice versa? – frogatto Oct 01 '14 at 17:45
  • I want the functions of helper to be available to each instance of parent - so I want helper to extend parent, but there may be an instance where I have, say sibling, being a sibling of parent, that also needs some functions within helper, where helper uses private variables of whichever function calls it ('this' in parent !== 'this' in sibling, but 'this' in either parent or sibling is accessible by helper). – Lazerblade Oct 01 '14 at 18:13

2 Answers2

1

What you are referring to is called a circular reference, which is defined as two data structures that both reference each other.

var a = {value: 'A'};
var b = {value: 'B'};
a.b = b;
b.a = a;

console.log(a.b.a.b.a.b.a.b.a.b.a.b.value); // 'B'

And in most cases, this is actually okay. Modern garbage collectors are smart enough to clean this up when the objects expire, so memory should be handled fine. Lots of JS code out there ceates objects with circular references.

The biggest problem you'll hit is if you want to encode the object to JSON, or do anything recursive with the object.

// continued code form above
JSON.stringify(a);
// TypeError: Converting circular structure to JSON

Because JSON generation is a recursive process, it's drilling into every property which would get locked in the circular reference and generate an infinite amount of JSON.

There are some ways handle that though

Community
  • 1
  • 1
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • My biggest concern is that, though modern garbage collectors in most browsers can handle this fine, will this lead to a memory leak or excessive memory use, and more importantly, will it impact performance on devices with limited memory (mobile, tablets, older desktops / laptops)? – Lazerblade Oct 01 '14 at 18:22
  • Most phones/tablets made in the last 5 years will run more modern JS engines in their browsers than old desktop versions of IE 6,7,8 where the big leaks happen. [More info here](http://stackoverflow.com/questions/1999840/javascript-circular-references-and-memory-leaks). Honestly, I wouldn't worry about it. If your code has memory issues, it wont be because of circular references. – Alex Wayne Oct 01 '14 at 18:27
  • Well, thank you for your answer. I was hoping for an alternative way of doing this, but if it's fine to do it the way that I did, then I guess I shouldn't be concerned about the "cascading", or circular, reference. I'll give others a chance to offer alternatives before marking your answer, but if nobody else can offer a better solution, you win. ;) – Lazerblade Oct 01 '14 at 18:32
  • I would argue this is the best way in this case. I mean, there other ways to do similar things, but they all have other compromises or drawbacks. You could just roll your helper functions into `Parent` but you have less encapsulation. You could define all helper methods in the `Parent` constructor that give them privileged access to local variables, but that would use more memory and be a bit slower since you can't reuse previously defined prototype methods. For example. I think the structure you have now is your best bet. – Alex Wayne Oct 01 '14 at 18:46
  • Although I've chosen to go a different route, I see no reason to not mark this answer as correct, as it does work fine in modern browsers. Due to a need for as much backwards compatibility as possible, and possible future needs, my code is posted below, but Alex has provided a logical and reasonable explanation. – Lazerblade Oct 02 '14 at 12:01
0

I made some adjustments to my code and after console.logging the results, it appears to be much cleaner. I'm posting this as a potential answer because, well, I could edit my question but I'd like to keep the feedback separate between question and answer, and see if anyone has any potential changes or alternatives, other than those already posted, that may provide a better solution.

For now, I'm going to go with this:

var Parent = function(identity) {
    if (identity) {
        this.identity = identity;
        this.group = $(this.identity);
        if (this.group.length > 0) {
            this.elems = new Array();
            this.helper = new Helper(this);
            this._setup(true);
        }
    }
    return this;
};

and

var Helper = function(reference) {
    for (var key in reference) {
        if (!this[key]) {
            this[key] = reference[key];
        }
    }
};

and extend accordingly from there.

Lazerblade
  • 1,119
  • 7
  • 17
  • Your going to just copy all the properties to the helper? That avoids circular references but if any of the properties of `parent` change after initialization, the `helper` wouldn't know. It would be much cleaner to just put the helper methods directly on `Parent` if you're going to go that route. – Alex Wayne Oct 01 '14 at 22:32
  • The thing is, I'm not calling any of the helper functions from within helper itself, I'm calling them from the parent. For instance, I have a helper function that changes the state of a button once pressed to show the user that the button has been pressed, and that it's waiting for a response (an ajax call is made). The helper is spoon-fed the object it needs to change the button state. I will also be calling the button function from a sibling of the parent, using the same method. The purpose of the helper is to cut down on repeat coding, not cross-share variables, per se. Feed in, get return. – Lazerblade Oct 02 '14 at 11:49
  • Furthermore, the website I'm working on has to be compatible with IE8 (unfortunately) and have as little of a footprint as possible. Though your link above to the memory leak issues doesn't directly correlate with what I'm doing, it could have an impact, so I'm hoping to avoid that issue by keeping it simple. – Lazerblade Oct 02 '14 at 11:56