I have searched around for this but thus far have not been able to find a duplicate, I may be using the wrong keywords...
I am trying to temporarily change a function stored in an object, but am having trouble setting it back to what it was before.
Consider this:
// Set the options object
var options = {
success: function(){
console.log('Original Function Called');
}
}
// Save the options
$('#foo').data('bar',options);
And then in another function:
// Get the options
var options = $('#foo').data('bar');
// Store the old options
var old_options = options;
// Temporarily change the success function
options.success = function(){
console.log('Temporary Function Called');
}
// Save the options
// This allows the other functions to access the temporary function
$('#foo').data('bar',options);
// Do stuff here that uses the new options
// Reset the options to include the original success function
$('#foo').data('bar',old_options);
I would have expected that to only display Temporary Function Called
once, however, it seems to completely replace the old success
callback with the temporary callback.
Can anyone tell me why and how I can get around this?
UPDATE
I thought that extend
would fix this but it seems that the issue may be a little deeper. I have decided to post a snippet of my actual code this time. Please be aware of the following before reading:
SM
is pretty much just an alias ofjQuery
, please ignore it.success
anderror
are parameters supplied to the function
Here is my code:
// Get the properties
var properties = $(form).data('autosave');
switch(parameter){
case 'save':
var old_properties = $.extend({},properties);
// Set the new callbacks if they have been supplied
properties.options.success = typeof success!=='undefined' ? success : old_properties.options.success;
properties.options.error = typeof error!=='undefined' ? error : old_properties.options.error;
// Save the properties
$(form).data('autosave',properties);
// Call the save method before setting the interval
SM(form)._as_save();
properties = $.extend({},old_properties);
// Save the old properties
$(form).data('autosave',properties);
// Clear the current interval
clearInterval(properties.interval);
// Call the save method periodically
properties.interval = setInterval(function(){
SM(form)._as_save();
},properties.options.interval);
break;
}
// Save the properties
$(form).data('autosave',properties);
I have searched around for this but thus far have not been able to find a duplicate, I may be using the wrong keywords...
I am trying to temporarily change a function stored in an object, but am having trouble setting it back to what it was before.
Consider this:
// Set the options object
var options = {
success: function(){
console.log('Original Function Called');
}
}
// Save the options
$('#foo').data('bar',options);
And then in another function:
// Get the options
var options = $('#foo').data('bar');
// Store the old options
var old_options = options;
// Temporarily change the success function
options.success = function(){
console.log('Temporary Function Called');
}
// Save the options
// This allows the other functions to access the temporary function
$('#foo').data('bar',options);
// Do stuff here that uses the new options
// Reset the options to include the original success function
$('#foo').data('bar',old_options);
I would have expected that to only display Temporary Function Called
once, however, it seems to completely replace the old success
callback with the temporary callback.
Can anyone tell me why and how I can get around this?
UPDATE
I thought that extend
would fix this but it seems that the issue may be a little deeper. I have decided to post a snippet of my actual code this time. Please be aware of the following before reading:
SM
is pretty much just an alias ofjQuery
, please ignore it.success
anderror
are parameters supplied to the function
Here is my code:
// Get the properties
var properties = $(form).data('autosave');
switch(parameter){
case 'save':
var old_properties = $.extend({},properties);
// Set the new callbacks if they have been supplied
properties.options.success = typeof success!=='undefined' ? success : old_properties.options.success;
properties.options.error = typeof error!=='undefined' ? error : old_properties.options.error;
// Save the properties
$(form).data('autosave',properties);
// Call the save method before setting the interval
SM(form)._as_save();
properties = $.extend({},old_properties);
// Save the old properties
$(form).data('autosave',properties);
// Clear the current interval
clearInterval(properties.interval);
// Call the save method periodically
properties.interval = setInterval(function(){
SM(form)._as_save();
},properties.options.interval);
break;
}
// Save the properties
$(form).data('autosave',properties);
Share
Improve this question
edited Jun 19, 2013 at 21:36
Ben Carey
asked Jun 19, 2013 at 20:51
Ben CareyBen Carey
17k20 gold badges93 silver badges179 bronze badges
5
|
4 Answers
Reset to default 10When you run this code:
var old_options = options;
you are not making a copy of the entire options
object that you can restore later. You are merely saving a reference to the same object. In other words, old_options
is the very same object as options
, so when you assign a new value into options.success
, you're changing it in both options
and old_options
—because they are the same object.
To fix this, you can use an object cloning function to make a copy of the object which you can then restore later. Since you're using jQuery, you can change the line above to:
var old_options = $.extend( true, {}, options );
Now, when you change options.success
, you're only changing it in the options
object. old_options
is unaffected, so your later call will restore it successfully:
$('#foo').data('bar',old_options);
Interestingly enough, this may still work OK even if options.success
is an asynchronous callback (which sounds likely from the name). That's because whatever code calls that .success()
method later on, they should still be holding on to a reference to your modified options
object—even if you've restored the old one back into the element's data in the meantime. At least one could hope for that; if the other code digs back into the $().data()
to find the .success
callback then you'd be in trouble.
The $.extend()
call above does a "deep" (recursive) copy of the options
object. That is, if one of the properties inside options
is itself an object, it also clones that object instead of just copying a reference to it.
If you leave out the true
argument, $.extend()
does a shallow copy instead:
var old_options = $.extend( {}, options );
This still creates a new object and copies over all the properties from an existing object, but if one of those properties is itself an object it doesn't clone that object, it just copies a reference. This is more efficient if it works with the structure of the object you're using, otherwise you can use the deep copy.
If the properties/methods you need to save and restore are direct children of the main object, a shallow copy should be enough. Here's a case where you'd definitely need a deep copy:
{
url: 'test',
events: {
success: function( data ) {
// ...
}
}
}
Here we have an object with an events
property, and that property is itself an object with some properties/methods of its own (in this example an events.success()
method. If you do a shallow copy of this object, the original and the copy will share a common events
object. So if you did something like this:
options.events.success = function(...) {...};
You'd actually be updating that in both options
and old_options
. No good. That's where a deep copy is needed.
The problem is that options
has reference semantics:
// Store the old options
var old_options = options;
This comment lies. You do not have a copy of the old options; rather, you have another reference to the same options object (another name with which you can refer to it).
So when you overwrite options.success
, this change is also visible on old_options
. The code that uses .data
to store and revert the value of the options is redundant.
The only thing you need to do is this:
var old_success = options.success;
options.success = function() { /* whatever */ };
// code that uses the new success callback
options.success = old_success; // restore original value
When you do this var old_options = options
you're not copying options
values but a reference to it. From now on, old_options
and options
point to the same memory spot and changes on any of them affect to both.
Your issue is you are working with objects. You are passing around references to the object. The underlying object is the same, all you have done is add a variable that points at the same address.
You will have to clone the object. EG create a new object with the same properties and copy them over.
This post may be useful to you.
$('#foo').data('bar',old_options);
you are still seeingTemporary Function Called
? – crush Commented Jun 19, 2013 at 20:54success
method is actually anoptions.success()
method (and similarly forerror
). That is exactly the situation where a deep copy would be required. So are things still broken or did the deep copy do the trick? – Michael Geary Commented Jun 20, 2013 at 2:42success
callback was a first-level property of the main options object. But the actual code in your update is a bit different: it has aproperties.options.success
function - not a direct child object but two levels down. That's exactly the situation where a deep copy is needed (or at least a more specialized copy that would handle this nested object). – Michael Geary Commented Jun 20, 2013 at 9:42