Access parent's parent from javascript object

Viewed 118118

Something like

var life= {
        users : {
             guys : function(){ this.SOMETHING.mameAndDestroy(this.girls); },
             girls : function(){ this.SOMETHING.kiss(this.boys); },
        },
        mameAndDestroy : function(group){ },
        kiss : function(group){ }
};

this.SOMETHING is what I imagine the format is, but it might not be. What will step back up to the parent of an object?

12 Answers

JavaScript does not offer this functionality natively. And I doubt you could even create this type of functionality. For example:

var Bobby = {name: "Bobby"};
var Dad = {name: "Dad", children: [ Bobby ]};
var Mom = {name: "Mom", children: [ Bobby ]};

Who does Bobby belong to?

In this case, you could use life to reference the parent object. Or you could store a reference to life in the users object. There can't be a fixed parent available to you in the language, because users is just a reference to an object, and there could be other references...

var death = { residents : life.users };
life.users.smallFurryCreaturesFromAlphaCentauri = { exist : function() {} };
// death.residents.smallFurryCreaturesFromAlphaCentauri now exists
//  - because life.users references the same object as death.residents!

You might find it helpful to use something like this:

function addChild(ob, childName, childOb)
{
   ob[childName] = childOb;
   childOb.parent = ob;
}

var life= {
        mameAndDestroy : function(group){ },
        kiss : function(group){ }
};

addChild(life, 'users', {
   guys : function(){ this.parent.mameAndDestroy(this.girls); },
   girls : function(){ this.parent.kiss(this.boys); },
   });

// life.users.parent now exists and points to life

If I'm reading your question correctly, objects in general are agnostic about where they are contained. They don't know who their parents are. To find that information, you have to parse the parent data structure. The DOM has ways of doing this for us when you're talking about element objects in a document, but it looks like you're talking about vanilla objects.

Here you go:

var life={
        users:{
             guys:function(){ life.mameAndDestroy(life.users.girls); },
             girls:function(){ life.kiss(life.users.guys); }
        },
        mameAndDestroy : function(group){ 
          alert("mameAndDestroy");
          group();
        },
        kiss : function(group){
          alert("kiss");
          //could call group() here, but would result in infinite loop
        }
};

life.users.guys();
life.users.girls();

Also, make sure you don't have a comma after the "girls" definition. This will cause the script to crash in IE (any time you have a comma after the last item in an array in IE it dies).

See it run

As others have said, it is not possible to directly lookup a parent from a nested child. All of the proposed solutions advise various different ways of referring back to the parent object or parent scope through an explicit variable name.

However, directly traversing up to the the parent object is possible if you employ recursive ES6 Proxies on the parent object.

I've written a library called ObservableSlim that, among other things, allows you to traverse up from a child object to the parent.

Here's a simple example (jsFiddle demo):

var test = {"hello":{"foo":{"bar":"world"}}};
var proxy = ObservableSlim.create(test, true, function() { return false });

function traverseUp(childObj) {
    console.log(JSON.stringify(childObj.__getParent())); // returns test.hello: {"foo":{"bar":"world"}}
    console.log(childObj.__getParent(2)); // attempts to traverse up two levels, returns undefined because test.hello does not have a parent object
};

traverseUp(proxy.hello.foo);

If you can't modify the structure of the object because it's given to you (typically from a library), you can alway traverse recursively the object to find its parent.

/**
 * Recursively traverse the rootObject to find the parent of childObject.
 * @param rootObject - root object to inspect
 * @param childObject - child object to match
 * @returns - parent object of child if exists, undefined otherwise
 */
function findParent(rootObject, childObject) {
  if (!(rootObject && typeof rootObject === 'object')) {
    return undefined;
  }
  if (Array.isArray(rootObject)) {
    for (let i = 0; i < rootObject.length; i++) {
      if (rootObject[i] === childObject) {
        return rootObject;
      }
      const child = this.findParent(rootObject[i], childObject);
      if (child) {
        return child;
      }
    }
  } else {
    const keys = Object.keys(rootObject);
    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      if (rootObject[key] === childObject) {
        return rootObject;
      }
      const child = this.findParent(rootObject[key], childObject);
      if (child) {
        return child;
      }
    }
  }
  return undefined;
}

// tests

const obj = {
  l1: { l11: { l111: ['a', 'b', 'c'] } },
  l2: { l21: ['a', 1, {}], l22: 123 },
  l3: [ { l33: {} } ],
};

assert.equal(findParent(obj, obj.l1), obj);
assert.equal(findParent(obj, obj.l1.l11), obj.l1);
assert.equal(findParent(obj, obj.l2), obj);
assert.equal(findParent(obj, obj.l2.l21), obj.l2);
assert.equal(findParent(obj, obj.l2.l22), obj.l2);
assert.equal(findParent(obj, obj.l3[0]), obj.l3);
assert.equal(findParent(obj, obj.l3[0].l33), obj.l3[0]);
Related