Replace a one javascript object with another object - javascript

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.

Related

How can I prevent changes on the objects returned by the module functions from changing the module data itself in Javascript?

I am working with Node for the first time and I have two modules. One module defines an array of objects, and also has functions that are exported for use in the other module - including lookupbyID, lookupbyLastName, and addEmployee.
My issue is that when I call the functions from module 1 in module 2, which return objects from the original array and assign those objects to a variable, and then I modify that variable, it modifies the original data. Please see the following code:
Module 1:
const us = require('underscore')
var data = [
{id:1, firstName:'John', lastName:'Smith'},
{id:2, firstName:'Jane', lastName:'Smith'},
{id:3, firstName:'John', lastName:'Doe'},
]
exports.lookupByID = function (given_id) {
var found_id = us.findWhere(data, {id:given_id});
return found_id;
}
Module 2:
const employeeFunctions = require("./employeeModule");
var id_2_answer = employeeFunctions.lookupByID(2);
id_2_answer.firstName = 'Mary'
console.log(employeeFunctions.lookupByID(2))
As you can see, I changed the name of Jane to Mary. Even though I assigned the object to a variable, changing the variable changed the original object data, which I verified by printing the lookupbyID function a second time.
Can you help me understand why this happens? Can you help me understand possible ways to prevent this from happening? I would like to be able to assign the object to a variable, and be able to change the values within the variable without affecting the original data.
Thank you!
In js objects are assigned by reference so:
var foo = {bar:1};
var baz = foo;
baz.bar; // 1
baz.bar = 2;
foo.bar; // 2
Use Object.assign to clone objects instead:
var foo = {bar:1};
var baz = Object.assign({}, foo);
baz.bar; // 1
baz.bar = 2;
foo.bar; // 1
The short answer is that you can't - you've hit on a key aspect of programming: value vs reference. You can read more about it here but the long and short of it is that both of these variables point to the same object in memory, so changing it for one changes it for the other.
The solution will necessarily involve creating a new object - the neatest way is probably to use Object.assign to copy over values, but bear in mind that in your example, you'll probably need to delete your old entry too.

Unexpected behavior using jQuery.extend with custom objects as attributes

I am using jQuery (1.11.2) and $.extend to clone an object. This doesn't work as expected.
I have a source object containing two attributes with custom instances of functions (new function...) as values. These values seems to be copied as references where I expect them to be copied as values. See the code below.
var AnyObj = function(){
this.attribute = "anyAttribute";
};
var target = {
a: new AnyObj(),
b: new AnyObj()
}
//this prints "anyAttribute"
console.log(target.a)
//now I create a deep copy
var copy = $.extend(true, {},target);
//and I change the attribute of the COPY(!!!)
copy.a.attribute = "YAYA";
//this prints an object with the value YAYA
console.log(copy.a)
//this should NOT print an object with the value YAYA
console.log(target.a)
It seems this is a jQuery extend() bug. Refer to below for more details:
http://bugs.jquery.com/ticket/10014

Storing Reference to function in object/variable

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.

Javascript array becomes an object structure

I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify

reinitializing javascript object's properties

In my Javascript drag and drop build app, a variety of buildings can be built. The specific characteristics of these are all saved in one object, like
var buildings = {
house: ['#07DA21',12,12,0,20],
bank: ['#E7DFF2',16,16,0,3],
stadium: ['#000000',12,12,0,1],
townhall: ['#2082A8',20,8,0,1],
etcetera
}
So every building has a number of characteristics, like color, size, look which can be called as buildings[townhall][0] (referring to the color). The object changes as the user changes things. When clicking 'reset' however, the whole object should be reset to its initial settings again to start over, but I have no idea how to do that. For normal objects it is something like.
function building() {}
var building = new building();
delete building;
var building2 = new building();
You can easily delete and remake it, so the properties are reset. But my object is automatically initialized. Is there a way to turn my object into something that can be deleted and newly created, without making it very complicating, or a better way to store this information?
You can keep initial state as a prototype on an object.
var Base = function(){};
Base.prototype={a:1,b:2};
var c = new Base();
Now, you can change value of a or b to whatever you want.
To restore, just use
delete c.a or delete c.b
You will get your initial value back.
Hope this help.
Just use a copy/clone method to restore the original state
var defaults = {
foo: "bar"
};
var building;
function reset(){
building = {};
for (var key in defaults) {
if (defaults.hasOwnProperty(key){
building[key] = defaults[key];
}
}
}
Now you can just call reset() whenever you need to have building reset.

Categories