Manipulating object in array changes object outside of array? - javascript

I don't understand why this behavior is happening. Lets say I define an object and make an array of 3 of this object. If I modify the objects in the array, it affects all instances of the object? Could someone explain why this is? Also, how do I make an array with independent "Copies" of the object to get the desired behavior? Thanks!
example
testObject = {"value1":"a","value2":"b"};
objArray = [];
for(i=0; i < 3; i++){
var newobj = testObject; //make a new testObject
objArray.push(newobj); //push new object to array
}
delete objArray[0].value2 // Desired, delete value 2 ONLY from array object 0
objArray[2].value2 //Undefined? Why is value2 missing from object 2
testObject.value2 //Undefined? Why is value2 missing from original object?

As opposed to primitives (strings, numbers, booleans, symbols null, undefined), objects in javascript are passed by reference. Variables serve as placeholders/pointers to these objects. To create a copy of an object without the risk of mutation you'd use spread (barring compatibility):
const newObject = { ...testObject };
or traditionally, Object.assign(), passing an empty object literal to avoid mutability of the original testObject:
const newObject = Object.assign({}, testObject);
As far as deep cloning, MDN suggests using a combination of JSON.parse() and JSON.stringify(). So for example:
const testObject = { value: "a", other: { value2: b } };
const newObject = JSON.parse(JSON.stringify(testObject));

You are pushing same object's reference again and again in the loop.
for(i=0; i < 3; i++){
var newobj = testObject; //no new object,same object's reference again
objArray.push(newobj); //push new object to array
}
It should be
for(i=0; i < 3; i++){
var newobj = {"value1":"a","value2":"b"}; //make a new testObject
objArray.push(newobj); //push new object to array
}

When creating an Object in JavaScript, you are actually creating a reference to that object. You can store this in a variable and pass it around ... perhaps append it to an array. When you go to do operations on an object reference, it finds the original object that the reference points to and updates it. Thus when you use .push it's not creating a new object but simply pushing the reference to that object. If you update it in one spot it will update it in the other and any others where you have assigned that reference.
Copying an object into a new object is generally called cloning. There are a lot of different ways to clone objects in JavaScript with varying results.
You can use var newobj = { ...testObject } as the other answer suggests. This spread operator essentially copies the properties of testObject and creates a new object (declared with the outer { }). The reference to that new object is then assigned to newobj. You can think of it as doing this:
var newobj = {
value1: testObject.value1,
value2: testObject.value2,
};
However, you should keep in mind that this gives you only one level of cloning. That is to say if your object contains other objects then the reference to that object will be assigned as the property rather than a clone of that object. For example: let's say you had:
var testObject = { obj: { a: "b" } };
var newobj = { ...testObject };
delete testObject.obj.a;
console.log(newobj); // { obj: {} }
In order to solve this, you need to do what is called a deep clone which in JavaScript can be done by recursively cloning object properties that are also objects. There are a bunch of ways to do this including libraries like lodash or home-grown functions. One example on SO: What is the most efficient way to deep clone an object in JavaScript?
Finally, if testObject is supposed to be something like an object template or initial state from which other newobj are derived, it might make more sense to use a function:
function newObjFactory() {
return {
value1: "a",
value2: "b",
};
}
Then you can do var newobj = newObjFactory() and you'll get a new object each time since a new object is created by the function each time it's called and returned.

Related

Object.create copying reference of array and object properties [duplicate]

This question already has answers here:
Crockford's Prototypal inheritance - Issues with nested objects
(3 answers)
Closed 7 years ago.
I am trying to understand how Object.create copies arrays and objects properties when initiating a new object. It seems to be different then copying a string or number. For example if we have a basic Object with a number and array property. jsfiddle example
var obj = {
num: 0, arr: []
};
We then initiate 3 new Objects from this base.
var set1 = Object.create(obj);
set1.num = 10;
set1.arr.push(1);
var set2 = Object.create(obj);
var set3 = Object.create(obj, {arr: []});
I was expecting set2.num and set2.arr property to be it's initial state. I found this to be true for the number, but not the array. Of course one work around is to pass {arr: []} when initiating the Object or creating a initiation function that resets the arr property.
// false
console.log(set1.num === set2.num);
// true - why is this true???
console.log(set1.arr === set2.arr);
// false
console.log(set1.arr === set3.arr);
Is this the normal behavior? Is Object.create keeping a reference to all of the Object's array and object properties? It would be very nice to not have to create new arrays and objects when initiating a new Object.
It would be very nice to not have to create new arrays and objects when initiating a new Object
Write a function in your favourite style
Returning a literal
function makeMyObject() {
return {num: 0, arr: []};
}
// usage
var obj = MyObject();
Returning an Object.created object, and assigning to it,
function makeMyObject() {
var o = Object.create(null); // or some prototype instead of `null`
return Object.assign(o, {num: 0, arr: []});
}
// usage
var obj = MyObject();
Using new
function MyObject() {
this.num = 0;
this.arr = [];
}
// usage
var obj = new MyObject();
Cloning is a bit more complicated, a basic example might be
function shallowClone(o) {
var e;
if (typeof o !== 'object')
return o;
e = Object.create(Object.getPrototypeOf(o));
// copy enumerable references
Object.assign(e, o);
// or to keep non-enumerable properties
// Object.defineProperties(b, Object.getOwnPropertyNames(o).map(Object.getOwnPropertyDescriptor.bind(Object, o)));
return e;
}
Deep cloning requires looping over properties (e.g. for..in for enumerable only) and type checking instead of simply copying everything over. You usually end up needing to recurse on properties which are Objects themselves.
For known types, you can teach it to use the correct constructor too, e.g.
if (Array.isArray(o)) {
e = [];
o.forEach((v, i) => e[i] = recurse(v));
}
Where recurse would be the name of the clone function

assigning a new object and keeping the reference?

consider this:
var o = { a: 1 }
read(o);
write(o);
read(o);
function read(x) {
console.log(x);
}
function write(x) {
x = { a: 2 };
}
Obviously the result is:
Object { a=1 }
Object { a=1 }
The write Function destroys the reference to the object by assigning a new object. Is there a way to do both - assigning a new object and keeping the reference? I need this to concat two Float32Arrays. As far as I could find out there is no possibility to concat them without creating a new Float32Array. Or is there one? They are supposed to be fast, but to me it looks rather slow if I always have to create a new one if I want to combine two fragments. But maybe this is another question.
How about this:
function overwrite(x,y){
for (var prop in y) {
if( y.hasOwnProperty(prop) ) {
x[prop] = y[prop];
}
}
}
Example: overwrite(o,{a:2});
(ES6) To insert values from one array into a typeed array, starting at an arbitrary index, try the typed array set() method.
// dest ... destination typed array
// scr ... array like object with elements to add
dest.set(src, dest.length); // append src elements to dest typed array

javascript returning difference between two objects

I have an object that looks like this:
var MyObject = {
prop1 = 12345,
prop2 = "string1",
ListOfOtherObject = an array of another type of object,
ListOfAnotherObject = an array of objects}
Let's say I have two objects: Object1 and Object2. Object2 was initially a deep-copy of Object1 and it was modified through the user's interactions with the UI. I'm looking to get the difference between both objects, especially when it comes to the arrays.
For instance, ListOfOtherObject in Object2 might contain a modified version of some objects as well as new objects.
I'm thinking about looping through each array and then looping through each object within but there might be some more efficient way to do it, especially with jquery. Or may be going with JSON.stringify and compares strings and retuns some sort of string difference. I was wondering if anyone had any suggestions on how to do this.
Thanks.
You need to specify what your comparison needs to do. For example, given:
var o = {name: 'fred'};
var p = {name: 'fred'};
var a = {o:o};
var b = {o:o};
then:
a == b; // false, a and b are different objects
a.o == b.o; // true since a.o and b.o reference the same object
but if comparing objects:
b.o = p;
a.o == b.o; // false since a.o and b.o reference different objects
or if comparing primitives:
a.o.name == b.o.name; // true since the value of both expressions is the string 'fred'
// even though a.o and b.o are different objects
Does Type or constructor matter? What about:
b.o = [];
b.o.name = 'fred';
a.o.name == b.o.name; // true or false? a.o is an object, b.o is an array

pushing javascript objects to arrays

I have a loop goes through an array of objects MyArrayOfObjects and then pushes the objects to a new array like this:
var NewArray = new Array();
for (i = 0; i < MyArrayOfObjects.length; i++) {
TempObject = null;
TempObject = new Object();
// I have logic that copies certain properties but not others
// but overall it looks like this:
TempObject.prop1 = MyArrayOfObjects[i].prop1;
TempObject.prop2 = MyArrayOfObjects[i].prop2;
NewArray.push(TempObject);
}
As I loop through MyArrayOfObjects, I clear the TempObject and create a new one each time. Does NewArray contain the objects that I'm copying or just a reference to the objects copied and that then become deleted as the loop iterates?
Thanks.
It contains references to the objects themselves.
This code shows that concept in action (notice that changing the object after pushing it into the array changes the object in the array as well):
var ray = new Array();
var obj = { foo: 123 };
ray.push(obj);
obj.foo = 321;
alert(ray[0].foo);
> var NewArray = new Array();
It is generally considered better to use an array literal to create an array. Variable names starting with a capital letter are, but convention, used for constructors. Using "new" at the start of a variable name can easily slip to become "new Array", and the name should reflect its purpose, so something like the following might be better:
var objectArray = [];
.
> for (i = 0; i < MyArrayOfObjects.length; i++) {
You should always declare variables, especially counters as undeclared variables are made properties of the global object (effectively global variables) when they are first assigned a value. Also, it is considered better to store the length of the array than get it in each iteration:
for (var i = 0, iLen = MyArrayOfObjects.length; i < iLen; i++) {
.
> TempObject = null;
> TempObject = new Object();
Again, declare variables. Assigning a value of null serves no useful purpose when you're going to assign some other value immediately afterward. Just do the second assignment (and use a literal):
var TempObject = {};
.
> // I have logic that copies certain properties but not others
> // but overall it looks like this:
>
> TempObject.prop1 = MyArrayOfObjects[i].prop1;
> TempObject.prop2 = MyArrayOfObjects[i].prop2;
>
> NewArray.push(TempObject);
At this point, TempObject and NewArray[NewArray.length - 1] both reference the same object.
> }
As I loop through MyArrayOfObjects, I clear the TempObject and create
a new one each time.
There is no need to "clear" the object, just assign a new value to the variable. In javascript, all variables have a value that might be a primitive (e.g. string, number) or a reference to an object (e.g. Object, Array, Number, String)
Does NewArray contain the objects that I'm
copying or just a reference to the objects copied and that then become
deleted as the loop iterates?
It contains references to the new objects created on each iteration.
As variables hold references to objects, assigning a new value to the variable doesn't do anything to the object. When an object is no longer referenced by any variable or object property, it is made available for garbage collection and may be removed automatically at some later time when garbage collection runs.
Using map or its jquery counterpart might be a more idiomatic way of doing this. For example:
var oldArray = [
{ prop1: 1, prop2: 10 },
{ prop1: 2, prop2: 20 },
{ prop1: 3, prop2: 30 }
]
var newArray = $.map(oldArray, function(oldObj) {
return { newProp: oldObj.prop1 }
})
console.log(newArray)

How does for(var x in object) work if there is initially no var x in the object, initially?

I have an object, and I'm trying to see what's inside of it. So, I used print(object), which should possibly contain Spot: True, indicating that the cat Spot is alive. It returned [object object]. So, I tried, show(object), and I got Spot: True. I think that's right, but I'm not sure what the indexes are like. For example, I'm not sure if the keys are associative or numeric, or even if associative arrays are allowed in JavaScipt.
The reason I wonder why is because for (var cats in object){show(cats);} returns Spot. I can't find a way to locate the string 'cat' as being part of the array.
The cats in your example is a new variable that holds each object of iteration.
And yes, "associative arrays" are allowed, but they're really just objects:
var foo = {
bar: "baz"
}
alert(foo.bar);
alert(foo["bar"]);
Re: the for/in statement: it's more or less the same as the following, here using an array:
var cats;
var arr = [42, 69];
for (var i = 0; i < arr.length; i++) {
cats = arr[i];
alert(cats);
}
Or you can use for/in and it becomes:
for (cats in arr) {
alert(arr[cats]);
}
It's slightly different for arrays, but there's no "cats" in the array, either.
Fiddle
Javascript has arrays and objects. Arrays have numeric continuous indexes [0..length) and ordered while objects can have random indexes (strings, numbers) and are not necessarily ordered (depends on the implementation).
Using for(var key in obj) {} should only be used for objects and iterates over the properties the object has. You can use obj[var] to access the value of each property.
Note that it's useful to add an if(!obj.hasOwnProperty(key)) continue; check to the loop to ensure you do not hit properties introduced in the object's prototype.
If object is your object, I'm guessing it's not actually an array. The for (var x in object) could also be enumerating the elements (properties, functions, etc.) of your object.
Where does your object come from? What are show() and print()?
If I was trying to print out the properties of an object I might have something like this:
var myObject = {
property1: 'Test',
property2: 'Test2',
function1: function() {
// do something
}
};
for (var prop in myObject) {
if (myObject.hasOwnProperty(prop)) {
console.log(prop + ' = ' + myObject[prop]);
}
}
This should output the following:
property1 = Test
property2 = Test2
function1 = function() { // do something }
Here's a jsFiddle to show the example.
With that said, JavaScript doesn't really have associative arrays. What you can have is an object with property:value pairs, which is what I think you have.

Categories

Resources