I'm looking for patterns which have been found acceptable when working with instances of js objects on the same page. (If there is a thread already covering this, a link will be appreciated.)
The issue is one of reference. After an object/feature is instantiated, it has to be referenced at some point later.
I've seen jQuery people store a reference to the object on the target DOM element using data(). However, I'm interested in a framework agnostic option if possible.
This could be accomplished if there was a clean, viable way to generate an unique id for a DOM element. Alas, I have not found one yet.
So my question is: What is the best way to store reference to an object, via a DOM element, so that you can reference it at a future arbitrary time?
Hopefully this makes sense, and I'm not just rambling. :)
Thanks.
There is nothing stopping you from maintaining your own cache:
var cache = [];
function locate(el) {
// search for the element within our cache.
for (var i=0;i<cache.length;i++) {
if (cache[i].elem === el) {
return cache[i].data;
};
};
// if we get this far, it isn't in the cache: add it and return it.
return cache[cache.push({
elem: el,
data: {}
}) - 1].data;
};
// used to add data to an element and store it in our cache.
function storeData(el, data) {
var store = locate(el);
for (var x in data) {
store[x] = data[x];
};
};
// used to retrieve all data stored about the target element.
function getData(el) {
return locate(el);
};
and then use as follows:
storeData(document.getElementById("foo"), {
something: 4,
else: "bar"
});
var data = getData(document.getElementById("foo"));
alert(data.something); // "4";
Objects in JavaScript (unlike classical OOP languages) can be augmented. There's nothing wrong with that; that's the way JavaScript was designed to be used:
Write:
document.getElementById( 'foo' ).customAttribute = 5;
Read:
alert( document.getElementById( 'foo' ).customAttribute );
If you don't want to alter the original object, the only way to point at it is using a dictionary as pointed out in one of the previous answers; however, you don't need to do a linear search to find the object: it can be done in logarithmic time providing you use an ID per element (potentially not its HTML ID but a custom one)
Related
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?
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.
Although your code is so obsfucated (one-character variable names, abuse of the comma operator, etc.) that its hard to tell for sure, it seems that you need to fix a few things before moving on.
Properties prefixed with an underscore are meant to be private. They are subject to change, and by change I mean your app randomly breaking. Use the public API.
Parsing strings out by hand is a lot of work for seemingly little gain. Is the use case for get('page.id') over get('page').id really so compelling?
Your code is incomprehensible. This is the kind of output one would expect of a minifier: it makes it hard to understand what any of it does/is supposed to do.
Unless a third-party API is so integral to your application that replacing it would require a rewrite no matter what (e.g. google maps) or so well-known that it has umpteen clones (jquery), its is generally a good idea to wrap third-party library calls so you can change the library later.
I realize this does not answer your question, but its way too long for a comment and it would be remiss of me to not point out the bright red targets (plural) you've painted on your feet prior to polishing your firearm.
As for your actual question (post-edit), you're on the right track. But I'd make it a curried function so that you can dynamically access different properties. We're going to ignore for one minute the huge mistake that is accessing private properties just to get the point across:
function accessDataCache(cache) {
return function(dataLayer) {
return function(namespaceObj) {
return function(property) {
return namespaceObj[cache][dataLayer][property];
};
};
};
};
var getFn = accessDataCache('_cache')('_dataLayer')(someTool);
getFn('page');
You can now also mix and match if you need other stuff:
var getSomeOtherCachedThing = accessDataCache('_cache')('_someOtherThing')(someTool);
All of that is quite tedious to write out by hand, so I recommend using something like lodash or Ramda and .curry to achieve the effect:
var accessCacheData = R.curry(function(cache, dataLayer, namespaceObj, property) {
return namespaceObj[cache][dataLayer][property];
});
Which is the best way between:
var myClass = function() {
this.myContainer = function(){ return $(".container");}
this.mySubContainer = function(){ return this.myContainer().find(".sub"); }
}
AND
var myClass = function() {
this.myContainer = $(".container");
this.mySubContainer = this.myContainer.find(".sub");
}
Is there any concrete differences?
The memory problem arose when I have seen that my web page, that has enough javascript ( about 150KB of mine + libs ) takes more then 300-400MB of RAM. I'm trying to find out the problem and I don't know if this could be one of them.
function myClass{
this.myContainer = function(){ return $(".container");}
this.mySubContainer = function(){ return this.myContainer().find(".sub"); }
}
Here you will need to call it something like myClassInstance.myContainer() and that means jquery will search for .container element(s) any time you are using that function. Plus, it will create 2 additional closures any time you will create new instance of your class. And that will take some additional memory.
function myClass{
this.myContainer = $(".container");
this.mySubContainer = this.myContainer.find(".sub");
}
Here you will have myContainer pointing to an object which already contains all links to DOM nodes. jQuery will not search DOM any time you use myClassInstance.myContainer
So, second approach is better if you do not want to slow down your app. But first approach could be usefull if your DOM is frequently modified. But I do not beleave you have it modified so frequently that you may need to use second approach.
If there is a single variable you are trying to assign , then the second approach looks cleaner..
Yes they are different.
MODEL 1:
In the first model, myContainer is a function variable.It does not contain the jQuery object.You cannot call any of jQuery's methods on the objet. To actually get the jQuery object you will have to say
var obj = this.myContainer() or this.myContainer.call()
MODEL 2:
The second model stores the actual jQuery object.
try alerting this.myContainer in both models, u will seee the difference.
Yes this is different. after you fix your syntax error :
function myClass(){... // the parentheses
1st
When you create a new object var obj = new myClass(), you are creating two functions, and the jquery object is not returned until you call it
var container = obj.myContainer();
2nd
As soon as the object is initialized the dom is accessed and you have your objects cached for later use;
var container = obj.myContainer;
var obj = {};
obj.a = 1; // fire event, property "a" added
This question is different from this one, where ways to detect when an already declared property is changed, being discussed.
this is possible, technically, but since all current JS implementations that I know of are single threaded it won't be very elegant. The only thing I can think of is a brute force interval:
var checkObj = (function(watchObj)
{
var initialMap = {},allProps = [],prop;
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//make tracer object: basically clone it
initialMap[prop] = watchObj[prop];
allProps.push(prop);//keep an array mapper
}
}
return function()
{
var currentProps = [];
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//iterate the object again, compare
if (watchObj[prop] !== initialMap[prop])
{//type andvalue check!
console.log(initialMap[prop] + ' => ' watchObj[prop]);
//diff found, deal with it whichever way you see fit
}
currentProps.push(prop);
}
}
//we're not done yet!
if (currentProps.length < allProps.length)
{
console.log('some prop was deleted');
//loop through arrays to find out which one
}
};
})(someObjectToTrack);
var watchInterval = setInterval(checkObj,100);//check every .1 seconds?
That allows you to track an object to some extent, but again, it's quite a lot of work to do this 10/sec. Who knows, maybe the object changes several times in between the intervals, too.All in all, I feel as though this is a less-then-ideal approach... perhaps it would be easier to compare the string constants of the JSON.stringify'ed object, but that does mean missing out on functions, and (though I filtered them out in this example) prototype properties.
I have considered doing something similar at one point, but ended up just using my event handlers that changed the object in question to check for any changes.
Alternatively, you could also try creating a DOMElement, and attach an onchange listener to that... sadly, again, functions/methods might prove tricky to track, but at least it won't slow your script down as much as the code above will.
You could count the properties on the object and see if has changed from when you last checked:
How to efficiently count the number of keys/properties of an object in JavaScript?
this is a crude workaround, to use in case you can't find a proper support for the feature in the language.
If performance matters and you are in control of the code that changes the objects, create a control class that modifies your objects for you, e.g.
var myObj = new ObjectController({});
myObj.set('field', {});
myObj.set('field.arr', [{hello: true}]);
myObj.set('field.arr.0.hello', false);
var obj = myObj.get('field'); // obj === {field: {arr: [{hello: false}]}}
In your set() method, you now have the ability to see where every change occurs in a pretty high-performance fashion, compared with setting an interval and doing regular scans to check for changes.
I do something similar but highly optimised in ForerunnerDB. When you do CRUD operations on the database, change events are fired for specific field paths, allowing data-bound views to be updated when their underlying data changes.
Is there any kind of persistence framework for JavaScript and/or the Google v8 engine?
I want to store (serialize) a whole graph of objects (including, e.g., functions) and re-load it later. JSON is not sufficient, since it does not permit functions to be stored and permits only a tree-like structure (i.e. no two objects referencing the same object).
I need to be able to do that generically (i.e. without knowing the JavaScript code at the time at which I write my program embedding v8), since I want the user of my program to be able to customize it with JavaScript, but I need to store the state of my program (including the state of the customization) and re-load it later. Hence I need to store the state of the JavaScript engine.
Edit:
Example:
Suppose we have the following code:
var obj = { a: 4, b: function (x) { return x + this.a; } }
// ...
if ( ... ) { obj.a = 5; }
// ...
if ( ... ) { var c = 1; obj.b = function (x) { return x + this.a + c; } }
// ...
// now I want to serialize obj
Then is it (without any meta-information about the logic of the program) possible to serialize obj and later deserialize it such that obj.b (2) delivers the same result after deserialization as it did before serialization?
Second Edit: Note the closure.
Unfortunately, what you're trying to do is not currently possible in Javascript. The reason is that closures are not just objects, they're objects bound to an execution context.
Getting past the "this can't be done in javascript" issue and moving into the "what if wrote a patch for V8 to allow this" phase of the answer, this is conceptually difficult. Essentially, for every closure you'd serialize, you would have to serialize the Context object that the closure exists in. It'd be nice to be able to just serialize the HandleScope, but the nature of closures is that you can't reach inside them.
Okay, so let's say you've written a function that can serialize the Context that the closure exists in, and you can even deserialize it. What do you do with it?
The answer to that is 'not much'. Javascript can only be executed in a single context at a time. The closure that you've deserialized doesn't exists in the context that you're trying to pull it back into. You can't really pass data between contexts, and if your function has data bound to free variables, do you use the ones that exist in the deserializer-invoking context, or do you overwrite it with the deserialized context? Conceptually, this is a nightmare.
Ecmascript Harmony had considered giving us nearly-first-class continuations, but it's been pushed form the discussion which I rant about here, but this isn't going to happen any time soon.
HTML5 local storage allows persistence at client level through javascript.
I'm not sure if it will fit your needings, as to being able to store a function you'll need to somewhat give it some markup that allows you to deserialize it when retrieving it from storage (or maybe just store it as plain text and try to eval it on retrieval)
http://diveintohtml5.info/storage.html
I don't think persisting functions is a good practice. I can suggest you the below approach. Turn your JSON data to lets say some class like "MyData". You can find two functions fromJSON, toJSON which will do the magic you want.
var MyData = function(props){
this.temp = "a";
this.getTemp = function(){
return this.temp;
}
this.fromJSON = function(props){
if(props){
this.temp = props.temp;
}
}
this.toJSON = function(){
var props = {};
props.temp = this.temp;
return props;
}
this.fromJSON(props);
}
var obj = new MyData({"temp" : "b"});
var state = obj.toJSON();
// persist state about the object as JSON string
LOCALSTORAGE.put(state); // You can write some HTML5 local storage stuff to persist
var persistedState = LOCALSTORAGE.get(); // You can use the above HTML5 local storage stuff to read the persisted stuff
var newBornObj = new MyData(persistedState);
I'm using jQuery with the validators plugin. I would like to replace the "required" validator with one of my own. This is easy:
jQuery.validator.addMethod("required", function(value, element, param) {
return myRequired(value, element, param);
}, jQuery.validator.messages.required);
So far, so good. This works just fine. But what I really want to do is call my function in some cases, and the default validator for the rest. Unfortunately, this turns out to be recursive:
jQuery.validator.addMethod("required", function(value, element, param) {
// handle comboboxes with empty guids
if (someTest(element)) {
return myRequired(value, element, param);
}
return jQuery.validator.methods.required(value, element, param);
}, jQuery.validator.messages.required);
I looked at the source code for the validators, and the default implementation of "required" is defined as an anonymous method at jQuery.validator.messages.required. So there is no other (non-anonymous) reference to the function that I can use.
Storing a reference to the function externally before calling addMethod and calling the default validator via that reference makes no difference.
What I really need to do is to be able to copy the default required validator function by value instead of by reference. But after quite a bit of searching, I can't figure out how to do that. Is it possible?
If it's impossible, then I can copy the source for the original function. But that creates a maintenance problem, and I would rather not do that unless there is no "better way."
Storing a reference to the function
externally before calling addMethod
and calling the default validator via
that reference makes no difference.
That's exactly what should work.
jQuery.validator.methods.oldRequired = jQuery.validator.methods.required;
jQuery.validator.addMethod("required", function(value, element, param) {
// handle comboboxes with empty guids
if (someTest(element)) {
return myRequired(value, element, param);
}
return jQuery.validator.methods.oldRequired(value, element, param);
}, jQuery.validator.messages.required);
This should work too: (And the problem with this is solved)
var oldRequired = jQuery.validator.methods.required;
jQuery.validator.addMethod("required", function(value, element, param) {
// handle comboboxes with empty guids
if (someTest(element)) {
return myRequired(value, element, param);
}
return oldRequired.call(this, value, element, param);
// return jQuery.oldRequired.apply(this, arguments);
}, jQuery.validator.messages.required);
Function.prototype.clone = function() {
var fct = this;
var clone = function() {
return fct.apply(this, arguments);
};
clone.prototype = fct.prototype;
for (property in fct) {
if (fct.hasOwnProperty(property) && property !== 'prototype') {
clone[property] = fct[property];
}
}
return clone;
};
The only bad thing with that is that the prototype isn't cloned so you can't really change it...
I'm working on a way to clone any type of objects and I just have RegExp left to do.
So I'll probably edit tomorrow and put the entire code (which is kind of long and isn't optimised if you only use it for functions and objects.
And to comment other answers, using eval() is totaly stupid and is way too long and the Function constructor is kind of the same. Literrals are much better.
And including JQuery just for that, moreover if it doesn't work properly (and I don't think it does) isn't the brightest thing you can do.
Here is an updated answer
var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter
However .bind is a new feature of JavaScript there is however a workaround from Mdn
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Additionally it does not clone the function additional properties.
Note: As #gsnedder pointed out : the bound statement, "this" argument u supplied (blank {} above). Will persist for any future calls of the function, regardless of over-riding via the apply()/call() functions.
This can be used both to your advantage, or disadvantage depending on how you deal with it.
I think you're just missing a bit of scoping. Try this:
jQuery.validator.methods._required = jQuery.validator.methods.required;
jQuery.validator.addMethod("required", function(value, element, param) {
// handle comboboxes with empty guids
if (someTest(element)) {
return myRequired(value, element, param);
}
return jQuery.validator.methods._required.call(this, value, element, param);
}, jQuery.validator.messages.required);
(this should only be a comment for Can I copy/clone a function in JavaScript? ... unfortunately with rep < 50 can only post)
Function.prototype.clone=function(){
return eval( '('+this.toString()+')' );
}
suffices, or even
Object.prototype.clone=function(){
return eval( '('+this.toString()+')' );
}
thoughts on efficiency:
human efficiency is far more important than squandering human
resources to optimize or improve a machine's "life"
computers and automated systems are supposed to reduce human effort
not increase it
computational overhead must severely impact a result's palatability
for human consumption, by many many people, to justify investing
effort in optimizing code that often becomes more obscure, arcane and
so esoteric that it can no longer be understood by a stable of
programmers after hours of trying to "comprehend" the logic
on cloning: this question can be quite rhetorical
what does it mean to "clone" a javascript object? especially in the context of recursive function theory
a common theme to rationalize cloning is that it isolates and "protects" an originator from its doppelgänger changes
if a = new Object(); b = new Object(); are a and b clones? how about o = Object; a = new o(); b = new o();
is it really required?
where does cloning stop? are the prototypical attributes to be
isolated also so a cloned object's change of these does not affect
instances not associated with the cloned object? in which case
cloning must go all the way up the prototypical chain to the
primitive constructors which themselves would need to be somehow
cloned and isolated (one trick in a browser is to open a new window and repopulate it with the caveat that opener . etc. can still leak cross-effects)
if you want to clone a function , try this :
Function.prototype.clone=function(){
return eval('['+this.toString()+']')[0];
}
Sounds like there's no 'better way'. I guess you could try making a custom required function for your own eg:
jQuery.validator.addMethod("customRequired", function(value, element, param) {
return myRequired(value, element, param);
}, jQuery.validator.messages.required);
Sounds like you've already tried everything else. Apologies if I misunderstood the question.
Answering the previous post, when I need to share a constructor function, while keeping distinct prototype, I use a wrapper in a new Function :
`
init_from_arg_obj = function () {
return new Function('init', 'for (var i in init) this[i] = init[i];');
};
constructor_A = init_from_arg_obj();
constructor_A.prototype = {a: 'A'};
constructor_B = init_from_arg_obj();
constructor_B.prototype = {b: 'B'};
`
I run several tests to verify that this does not slow execution on good JS engines.