This question already has answers here:
Object copy using Spread operator actually shallow or deep?
(4 answers)
Closed 6 months ago.
I have a use case that I thought the Map object would be perfect for but there is confusing behaviour that appears to me as a bug, but Im after any information as to why this happens and possibly a solution to the behaviour.
For instance, say I have 2 objects:
const obj1 = { name: "John" };
const obj2 = { name: "Jane", age: 35 };
And I have defined and extra object for extra properties to add to both objects later:
const extProps = { gender: "unspecified", children: [] };
Create a new Map object and add the 2 objects:
const map = new Map();
map.set(obj1.name, obj1);
map.set(obj2.name, obj2);
Due to the objects being reference types I can assign the extra props like so:
Object.assign(obj1, { ...extProps });
Object.assign(obj2, { ...extProps });
Now I can get the values from the map using the keys like:
const johnObj = map.get("John");
const janeObj = map.get("Jane");
And the object have all the extra props like expected. The following statements update the individual objects in the map:
janeObj.gender = "female";
johnObj.age = 45;
Here is where the confusing behaviour I see is happening...
If I add an entry to the children array of either objects, it updates both
johnObj.children.push("jack");
obj2.children.push("jenny");
name: "John"
gender: "unspecified"
children: ["jack", "jenny"]
age: 45
name: "Jane"
age: 35
gender: "female"
children: ["jack", "jenny"]
What am I missing??
Like Konrad said in his comment, "arrays are also objects and are reference types".
The issue is that the spread operator (...) only goes on level deep, so for the array in extProps, is it not copied, it is the same one.
To solve this you can use a recursive function to "deep copy" an object.
Here is an example of a deep copy function:
const deepCopy = objIn => {
if (typeof objIn !== 'object' || objIn === null) return objIn;
let objOut = Array.isArray(objIn) ? [] : {};
for (const key in objIn) {
objOut[key] = deepCopy(objIn[key]);
}
return objOut;
}
Related
I have an array of objects, and want to add a new object only if that object doesn't already exist in the array.
The objects in the array have 2 properties, name and imageURL and 2 objects are same only if their name is same, and thus I wish to compare only the name to check whether the object exists or not
How to implement this as a condition??
Since you've not mentioned the variables used. I'll assume 'arr' as the array and 'person' as the new object to be checked.
const arr = [{name: 'John', imageURL:'abc.com'},{name: 'Mike', imageURL:'xyz.com'}];
const person = {name: 'Jake', imageURL: 'hey.com'};
if (!arr.find(
element =>
element.name == person.name)
) {
arr.push(person);
};
If the names are not same, the person object won't be pushed into the array.
You can use Array.find
let newObj={ name:'X',imageURL:'..../'}
if(!array.find(x=> x.name == newObj.name))
array.push(newObj)
You need to check it using find or similar functions like this:
const arr = [{ name: 1 }, { name: 2 }];
function append(arr, newEl) {
if (!arr.find(el => el.name == newEl.name)) {
arr.push(newEl);
}
}
append(arr, { name: 2 }); // won't be added
console.log(arr);
append(arr, { name: 3 }); // will be added
console.log(arr);
This question already has answers here:
How to convert an Object {} to an Array [] of key-value pairs in JavaScript
(21 answers)
Closed 19 days ago.
if we create one object like
const userDetails={firstname:'karan',lastname:'khimani'}
then expected output like
[["firstname", "karan"], ["lastname", "khimani"]]
How did i convert this?
Use Object.entries:
const userDetails = { firstname: "karan", lastname: "khimani" };
const arr = Object.entries(userDetails);
console.log(arr);
I believe this is an ES7 feature - so if you need to support older browsers, use map with Object.keys:
var userDetails = { firstname: "karan", lastname: "khimani" };
var arr = Object.keys(userDetails).map(function(key) {
return [key, userDetails[key]]
});
console.log(arr);
So what you want to do is create an array, which contains keys and values as arrays.
You should have a look at Object.keys and Object.entries.
Soluce are below, but try to find it yourself first looking at the documentation of the functions I've given you.
const userDetails = {
firstname: 'karan',
lastname: 'khimani'
};
const transformed = Object.keys(userDetails).map(x => [x, userDetails[x]]);
console.log(transformed);
Why not always use Object.entries? Because It's not well supported on every browser.
const userDetails = {
firstname: 'karan',
lastname: 'khimani'
};
const transformed = Object.entries(userDetails);
console.log(transformed);
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.
Came across the concept of creating new object using spread syntax as below
const human = { age: 20 };
const john = { ...human };
john.age = 10;
console.log(human.age); // 20
console.log(john.age); // 10
As shown above human object get to retain it's original value. Now have a look at below code:
const human = { age: 20, cars: ["toyota", "honda"] };
const john = { ...human };
john.cars[1] = "camero";
console.log(human.cars); // ["toyota", "camero"]
console.log(john.cars); // ["toyota", "camero"]
Can anyone explain to me why the above scenario happened? Why human's cars object get changed? It looks to me that it's very likely for developer to make mistakes without understanding how to avoid the inconsistent behaviour
The object human only contains a reference to the array which contains ["toyota", "honda"]. When you duplicate the object using the spread operator, you also duplicate the reference, which means that john has an identical reference and therefore john.cars is the same array as human.cars.
Because of this, if you modify john.cars, you also modify human.cars because they are the same array. If you want to clone an array you can also do this using the spread operator:
const human = { age: 20, cars: ["toyota", "honda"] };
const john = { ...human };
john.cars = [ ... human.cars ];
john.cars[1] = "camero";
console.log(human.cars); // ["toyota", "honda"]
console.log(john.cars); // ["toyota", "camero"]
You will also see this type of behaviour if you clone an object that has properties which are objects:
const human = { name: { first: "John", last: "Jackson" } };
const human2 = { ... human };
human2.name.first = "Ellen";
console.log(human.name.first); // Ellen
That's because the spread operator only copies a reference to the name object, not the name object itself. Therefore, modifying one modifies the other because they are the same object. This is referred to as shallow cloning. If you want to avoid this confusion, you need to perform a deep clone.
The easiest way to do this is to convert to JSON and then convert back:
const human = { name: { first: "John", last: "Jackson" } };
const human2 = JSON.parse(JSON.stringify(human));
human2.name.first = "Ellen";
console.log(human.name.first); // John
console.log(human2.name.first); // Ellen
To realize shallow copy in object in below code, but the different outputs confuse me:
Object.assign:
var obj = {
name: 'wsscat',
age: 0,
add: {
a: 'beijing'
}
}
var obj2 = Object.assign({}, obj);
obj2.age = 18;
obj2.add.a = 'shanghai';
console.log(obj)
console.log(obj2)
output:
{ name: 'wsscat', age: 0, add: { a: 'shanghai' } }
{ name: 'wsscat', age: 18, add: { a: 'shanghai' } }
while use = "assign" to realize shallow copy:
var obj = {
name: 'wsscat',
age: 0,
add: {
a: 'beijing'
}
}
// var obj2 = Object.assign({}, obj);
var obj2 = obj;
obj2.age = 18;
obj2.add.a = 'shanghai';
console.log(obj)
console.log(obj2)
output:
{ name: 'wsscat', age: 18, add: { a: 'shanghai' } }
{ name: 'wsscat', age: 18, add: { a: 'shanghai' } }
I think you meant 'shallow copy', not 'shadow copy', so I will reference to the former in my answer.
By using assignment operator = you just copy reference, so obj2 points to the same object as obj, so changing property is reflected on both.
The way you used Object.assign, creates shallow clone by copying all own properties from source object obj to target object (empty) which is then assigned to variable obj2.
Primitive data types (null, undefined, String, Number, Boolean) are copied by value, so keys name and age on both objects contain different values in memory). Objects types (Object, Arrray, Function) are copied by reference, so object under add property is shared between both obj and obj2. Any change in obj2.add.a will be reflected on obj.add.a.
Take a look at polyfill implementation of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assigne/
EDIT: Another link with polyfill implementation: https://gist.github.com/spiralx/68cf40d7010d829340cb
EDIT2: Clarified difference between copying primitive types and objects.
Well answered question from Smyk but i want to mention a simple thing.
as he mentioned :
first attempt was a shallow copy of an object which is a good way to avoid pointing to the same reference with flat objects (not nested).
second attempt clearly was pointing to a reference (Array and Object are passing the reference of the variable).
More about that :
there is a problem with shallow copy of objects, because nested object/array is a reference again so in order to fix this, you may have heard about deep copy which is a way to allocate a separate memory location/address for the new object.
an example for that :
var student1 ={
name : "Ahmed",
company : "Tech",
address: {
city: "Tokyo"
}
}
var student2 = JSON.parse(JSON.stringify(student1))
student1.address.city = "Erbil";
console.log(student1.address.city);
console.log(student2.address.city);
More information, here's a link
also to go in depth read this