slinkhi slinkhi - 3 months ago 18
Javascript Question

javascript referencing dynamic parent object

I have this object, a 3rd party tracking tool similar to google analytics. I want to extend it with my own "caching" function that saves the data from the previous tracking call so that I can reference stuff on the next tracking call if needed.

This is what I have so far, and it works:

// Current 3rd party tool, can't really mess with this.
// It is loaded from an external script
window.someTool={/* stuff */};

// my code
someTool._cache=someTool._cache||{};
someTool._cache._get=function(variabl) {
var length,index,variabl=(variabl||'').split('.'),
cache=someTool&&someTool._cache&&someTool._cache._dataLayer||{};
for (index=0,length=var.length;index<length;index++){
cache=cache[variabl[index]];
if (!cache) break;
}
return cache;
};


So then I have/do the following

// data layer output on initial page that gets wiped later
var dataLayer = {
'page' : {
'name' : 'foo',
'lang' : 'en'
},
'events' : {
'pageView' : true,
'search' : true
}
}

// I grab the initial data layer and save it here
someTool._cache._dataLayer = dataLayer;


This then allows me to do stuff like

someTool._cache._get('page'); // returns {'page':{'name':'foo','lang':'en'}
someTool._cache._get('page')['name']; // returns 'foo'
someTool._cache._get('page.lang'); // returns 'en'


So this works for me, but here comes the question/goal: I want to improve my
_get
function. Namely, I don't like that I have to hardcode
someTool
, or really even
_cache
, and if I can somehow swing it,
_dataLayer
.

Ideally, I'd like a reference of
someTool._cache._dataLayer
passed/exposed to
_get
(e.g. a
parent
type reference) so that if
someTool
,
_cache
, or
_dataLayer
were to change namespaces, I don't have to update
_get
. But I am not sure how to do that.

This is what I have so far:

(function(tool, cache, dataLayer) {
var tool = tool || {},
cache = cache || '_cache',
dataLayer = dataLayer || '_dataLayer';

dataLayer = tool[cache][dataLayer] || {};

tool[cache]._get = function(property) {
var length, index, property = (property || '').split('.');

for (index = 0, length = property.length; index < length; index++) {
dataLayer = dataLayer[property[index]];
if (!dataLayer) break;
}

return dataLayer;
};
})(someTool, '_cache', '_dataLayer');


This seems to work the first time I call it, e.g.

someTool._cache._get('page')['name'];
// returns 'foo'

But after that, I get an error:

TypeError: someTool._cache._get(...) is undefined


I feel like it has something to do with
dataLayer
losing its reference or something, I dunno (though I'm not sure how it's working first time around..). Is what I am doing even possible, and if so, where am I going wrong? Or is what I originally have the best I can do?

Answer

I feel like it has something to do with dataLayer losing its reference or something, I dunno (though I'm not sure how it's working first time around..).

The reason this is happening is because you are using the same dataLayer you initialize in the closure of _get to:

  • store information, and
  • to use as a temporary loop variable

If you look at your code:

(function(tool, cache, dataLayer) {
  // ...

  // Here you are initializing (or looking up) the dataLayer
  dataLayer = tool[cache][dataLayer] || {};

  tool[cache]._get = function(property) {
    // ...

    for (index = 0, length = property.length; index < length; index++) {
      // here you are overwriting the same dataLayer
      dataLayer = dataLayer[property[index]];
      if (!dataLayer) break;
    }

    return dataLayer;
  };
})(someTool, '_cache', '_dataLayer');

You can see that your loop will overwrite dataLayer on each iteration which means every lookup after the first will most likely be wrong.

Eventually, dataLayer will be overwritten with undefined, and then any further lookups will now break the code.

What you can do is use another variable for the loop iteration:

var temp;
for (index = 0, length = property.length; index < length; index++) {
  temp = dataLayer[property[index]];
  if (!temp) break;
}

return temp;

This will leave your dataLayer object intact.

Comments