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 of jQuery, please ignore it.
success and error 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);
When 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.
Related
I've defined an enumerable property in the prototype object and would like it to appear when I convert a prototyped object to JSON.
My first idea was to set it in toJSON but because I don't really want to keep it in the object afterwards I'll have to more or less clone the whole object in the function and set the necessary property.
Redefining the property in the target object and just proxying with the context of the current object doesn't seem to be an option as well, since I can't really use apply or call when getting dynamic properties.
Working solutions I could come up with so far seem to require quite an amount of code and aren't flexible and concise enough, so I'm wondering if there are any best practices of solving this task.
Here is an example which could seem a bit synthetic but still, I believe, conveys the idea:
function ProjectFolder() {
this.files = [];
Object.defineProperty(this, 'size', {enumerable: true, get: function() {
return this.files.length;
}});
}
function GithubProjectFolder() {
this.files = ['.gitignore', 'README.md'];
}
GithubProjectFolder.prototype = new ProjectFolder();
var project1 = new ProjectFolder();
JSON.stringify(project1);
// output: {"files":[],"size":0}
// size is present
var project = new GithubProjectFolder();
JSON.stringify(project);
// output: {"files":[".gitignore","README.md"]}
// size is absent
I'll have to more or less clone the whole object in the function and set the necessary property.
Yes, and there's nothing wrong with that. That's how .toJSON is supposed to work:
ProjectFolder.prototype.toJSON = function toJSON() {
var obj = {};
for (var p in this) // all enumerable properties, including inherited ones
obj[p] = this[p];
return obj;
};
However, there are two other points I'd like to make:
The size of a folder doesn't really need to be stored separately in the JSON when it already is encoded in the length of the files array. This redundant data seems to be superfluous, and can confuse deserialisation. Unless something requires this property to be present, I'd recommend to simply omit it.
In ProjectFolders, the .size is an own property of each instance - in GithubProjectFolders it is not. This suggest that you're doing inheritance wrong. Better:
function GithubProjectFolder() {
ProjectFolder.call(this);
this.files.puhs('.gitignore', 'README.md');
}
GithubProjectFolder.prototype = Object.create(ProjectFolder.prototype);
If you'd fix that alone, the size will appear in the serialisation of your project.
I have been coding in javascript for some time, but am fairly new to Node. I recently undertook a project that involves a complex object structure with multiple levels of prototypical inheritance and sub objects. This structure needs to be periodically saved / loaded. Saving and loading in JSON is desirable.
The Question
Is there a more elegant way of accomplishing the task of saving/loading these complex Javascript objects than my current method (outlined below)? Is it possible to design it in such a way where the constructors can initialize themselves as if they were normal objects without being bound by all of the restoring functionality?
My Solution
The base 'class' (from which, by design, all other objects under consideration inherit protoypically) has a function which processes an 'options' argument, adding all of it's properties to the current object. All deriving objects must include an options argument as the last argument and call the processing function in their constructor.
Each object also must add it's function name to a specific property so that the correct constructor function can be called when the object needs to be rebuilt.
An unpack function takes the saved object JSON, creates a plain object with JSON.parse and then passes that object in as the 'options' argument to the object's constructor.
Each object is given a unique id and stored in a lookup table, so that a function under construction with links to other objects can point to the right ones, or create them if it needs to.
Here is a plunker which demonstrates the idea (obviously in a non-Node way).
If you don't want to load the plunker, here's an excerpt which should hopefully provide the gist of what I'm trying to do:
function BaseClass(name, locale, options){
if(name) this.name = name;
if(locale) this.locale = locale;
// If options are defined, apply them
this.processOptions(options);
// create the classList array which keeps track of
// the object's prototype chain
this._classList = [arguments.callee.name];
// Create a unique id for the object and add it to
// the lookup table
if(!this.id) this.id = numEntities++;
lookupTable[this.id] = this;
if(!this.relations) this.relations = [];
// other initialization stuff
}
BaseClass.prototype = {
processOptions: function(options) {
if(options && !options._processed){
for(var key in options){
if(options.hasOwnProperty(key)){
this[key] = options[key];
}
}
options._processed = true;
}
},
addNewRelation: function(otherObj){
this.relations.push(otherObj.id);
}
// Other functions and such for the base object
}
function DerivedClassA(name, locale, age, options){
if(age) this.age = age;
this.processOptions(options);
if(options && options.connectedObj){
// Get the sub object if it already exists
if(lookupTable[options.subObj.id]){
this.subObj = lookupTable[options.subObj.id];
}
// Otherwise, create it from the options
else {
this.subObj = new OtherDerivedClass(options.subObj);
}
}
else {
// If no options then construct as normal
this.subObj = new OtherDerivedClass();
}
// If something needs to be done before calling the super
// constructor, It's done here.
BaseClass.call(this, name, locale, options);
this._classList.push(arguments.callee.name);
}
DerivedClassA.prototype = Object.create(BaseClass.prototype);
As mentioned, this gets the job done, but I can't help but feeling like this could be much better. It seems to impose a ridiculous amount of restrictions on the inheriting 'classes' and how their constructors must behave. It makes a specific order of execution critical, and requires that each object be deeply involved and aware of the restoration process, which is far from ideal.
I am serializing and storing an object that was created from a WinJS.Class like this:
var myClass = WinJS.Class.define(...);
var myObject = new myClass();
var serialized = JSON.stringify(myObject);
//store the object
And later I'm pulling the object out of storage and I want to deserialize it and cast it as a myClass. Is that possible with WinJS out of the box or do I need to create a constructor for my class that is capable of taking an object that can turn it into a new object?
I haven't broken into TypeScript yet, and I think that would help out in this situation, but until then I'm wondering how to do it with plain JavaScript/WinJS.
There are a few ways to handle this, and none are particularly special to WinJS. Simply put: JSON serialization only serializes and deserializes the obje values, not its methods, prototype, or other type information.
Option 1: Copy values to new instance of your class
This is usually best accomplished by having your constructor take the deserialized object as a parameter and copying the data to the new instance.
There are a variety of variations of this. Using the object constructor is generally the best for performance, as this typically enables the JS engine to apply the greater number of optimizations to the object.
WinJS.UI.setOptions can be helpful here, or you can just copy the data using a simple loop like this:
var keys = Object.keys(source);
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
destination[key] = source[key];
}
Option 2: Setting __proto__
Warning: This can have significantly adverse performance effects, so it's not appropriate in some situations. But occasionally it can be handy.
Object.setPrototypeOf(myObject, myClass.prototype);
Note that setPrototypeOf is relatively new. It's there on Win8.1 for web apps (which I'm guessing this is about) and in IE 11, but not available in Safari, for example. On older browsers/ Safari, assigning to proto is the equivalent (but if available, setPrototypeOf is better).
This will attach methods from myClass to the object, but in addition to the negative performance effects, also does not run your constructor on the object - so it still may not be in exactly the same state as the object you originally serialized.
Other helpful thing: JSON "revivers"
JSON.parse takes an optional second parameter, called a "reviver". This lets you provide a function that gets the opportunity to transform each node of the JSON being deserialized. This can be useful for rehydrating serialized dates into JavaScript Date objects, for example. It also gets the opportunity to transform the top-most object, which could be useful in some cases to turn the deserialized object into the "class" you want.
Javascript is a dynamic language so I think you dont need to cast the deserialized object, just treat it as myClass type and that's it. Hope it helps you.
You should consider using the 'Options' constructor pattern, where the option value is the deserialized object:
// MovieModel Constructor
// ----------------------
function MovieModel(options) {
this._titleValue = options.title || "Sample Title";
}
Where the movie methods closure is something like this:
// MovieModel Methods
// ------------------
var movieModelMethods = {
title: {
get: function () {
return this._titleValue;
},
set: function (val) {
this._titleValue = val;
this.dispatchEvent("title");
}
}
};
Since WinJS class define can only specify one constructor function (as far as I understand it), you may use the static members to define a factory function that will take the serialized data as a parameter. This factory methdod will actually create a new instance and will set the values one by one and return the new object.
It as some advantages like the fact that you can actually manage the data structure changes over the time you enhance the app...
The drawback is that you cannot write new MySuperClass() all the time...
...
// let's suppose we already called JSON.parse(data);
create: function(serializedData) {
var newObj = new MySuperClass();
newObj.name = serializedData.name || "";
newObj.color = serializedData.color || "";
return newObj;
}
Then you will call somewhere else in the app :
var myInstance = MySuperClass.create(serializedDataFromfile);
You should just be able to call JSON.parse after pulling it out of local storage:
var myObject2;
myObject2 = JSON.parse(localStorage["mySeriazliedObject"];
On page load I am creating two Javascript Objects, objDemo1 and objDemo1Backup where the latter is simply an exact copy of the first.
e.g.
objDemo1 {
sub_1 = { something: 123, somethingElse: 321 },
sub_2 = { something: 456, somethingElse: 654 }
}
I can modify the values in sub_ as well as add / delete new sub_'s but the only object I am editing is objDemo1. i.e. I never change objDemo1Backup
I have a reset button that when clicked will reset objDemo1 back to what it was when the page originally loaded (i.e. objDemo1 = objDemo1Backup). This is where I am having the issue..
How do I set objDemo1 to objDemo1Backup?
I have tried:
objDemo1 = objDemo1Backup;
and
objDemo1 = null;
var objDemo1 = objDemo1Backup;
...as well as similar variations but nothing seems to work.
Any ideas?
Note: I can confirm that at the point of resetting, objDemo1Backup is exactly the same as it was when I created it and objDemo1 has changed.
My code is definetly hitting the "reset" functionality, where I've tried the objDemo1 = objDemo1Backup... I just cannot figure out the syntax to replace the object.
I'm using angularjs and it took me some time to find out how to copy an object to another object. Normally you'll get an objects clone by calling clone or here in angular copy:
var targetObj = angular.copy(sourceObj);
This gives you a new cloned instance (with a new reference) of the source object. But a quick look into the docs reveals the second parameter of copy:
angular.copy(sourceObj, targetObj)
This way you can override a target object with the fields and methods of the source and also keep the target objects reference.
In JavaScript objects are passed by reference, never by value. So:
var objDemo, objDemoBackup;
objDemo = {
sub_1: "foo";
};
objDemoBackup = objDemo;
objDemo.sub_2 = "bar";
console.log(objDemoBackup.sub_2); // "bar"
To get a copy, you must use a copy function. JavaScript doesn't have one natively but here is a clone implementation: How do I correctly clone a JavaScript object?
var objDemo, objDemoBackup;
objDemo = {
sub_1: "foo";
};
objDemoBackup = clone(objDemo);
objDemo.sub_2 = "bar";
console.log(objDemoBackup.sub_2); // undefined
You can use Object.assign.
ObjectConstructor.assign(target: T, source: U): T & U
It takes up two parameters: target and source. When function completes, all internals of target object will be appended with source one.
is there a way to copy a global object (Array,String...) and then extend the prototype of the copy without affecting the original one? I've tried with this:
var copy=Array;
copy.prototype.test=2;
But if i check Array.prototype.test it's 2 because the Array object is passed by reference. I want to know if there's a way to make the "copy" variable behave like an array but that can be extended without affecting the original Array object.
Good question. I have a feeling you might have to write a wrapper class for this. What you're essentially doing with copy.prototype.test=2 is setting a class prototype which will (of course) be visible for all instances of that class.
I think the reason the example in http://dean.edwards.name/weblog/2006/11/hooray/ doesn't work is because it's an anonymous function. So, instead of the following:
// create the constructor
var Array2 = function() {
// initialise the array
};
// inherit from Array
Array2.prototype = new Array;
// add some sugar
Array2.prototype.each = function(iterator) {
// iterate
};
you would want something like this:
function Array2() {
}
Array2.prototype = new Array();
From my own testing, the length property is maintained in IE with this inheritance. Also, anything added to MyArray.prototype does not appear to be added to Array.prototype. Hope this helps.
Instead of extending the prototype, why don't you simply extend the copy variable. For example, adding a function
copy.newFunction = function(pParam1) {
alert(pParam1);
};