How references of the same object can't be equal? - javascript

Eg.:
let userOne = {
name: "Test",
surname: "Test"
}
let userTwo = {
...userOne
}
console.log(userOne === userTwo); // false
But, eg.:
console.log(userOne.name === userTwo.name); // true
So userOne and userTwo are two refeneces of the same object but console.log(userOne === userTwo); returns fasle.
Why is it?
Modification:
Ok. In the previous example there are two object. But what about this:
let userOne = {
name: "Test",
surname: "Test surname",
sizes: {
width: 200,
height: 200,
}
}
let userTwo = {
...userOne
}
userTwo.sizes.width = 50;
alert(userOne.sizes.width); // 50
So userOne and userTwo are the references the same object.
But: alert(userOne == userTwo);// false
So the the two references does not point to the same object?

Because thy are not references of the same object! They are two objects that (superficially) look the same.
Think any kind of store, you take a product from the shelf and there's a dozen identical products behind it.
Just because they look the same doesn't make them the same item.
let userTwo = userOne; creates a second reference to the same item.
let userTwo = {...userOne} creates a new object and copies the properties that are own and enumerable to that new object.
In the previous example there are two object. But what about this:
let userOne = {
name: "Test",
surname: "Test surname",
sizes: {
width: 200,
height: 200,
}
}
let userTwo = {
...userOne
}
userTwo.sizes.width = 50;
alert(userOne.sizes.width); // 50
here userOne.size === userTwo.size. so if you change one ... the other one is the same one.
but userOne !== userTwo.
You and your sibling can have the same father, and if he loses an arm, well that doesn't only apply to one of you. But that doesn't make you and your sibling the same person.

They aren't the same object.
You have two different objects, but the second one has the properties of the first copied into it with spread syntax.
It would be clearer to demonstrate if more properties were included in the second object.
let userOne = {
name: "Test",
surname: "Test"
}
let userTwo = { ...userOne,
yearOfBirth: 2000
};
console.log({
userOne
});
console.log({
userTwo
});
As you can see, the additional value is added to the second object but not the first.

The es6 spread operator {...userOne}, creates a copy of the object userOne and store it in different memory location
which is also equals to this,
let userTwo = Object.assign({}, userOne);
But if you assign the object to another,
let userThree = userOne;
This is going to return true.
This on the other hand, only compares the values
userOne.name === userTwo.name so it is returning true.

because of the logic you give in equals() method, by default they are equal.

Related

Problems with JavaScript object destructuring

let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = {};
({
name: ppppCopy.name,
age: ppppCopy.age,
job: ppppCopy.job
} = pppp);
pppp.job.title = "Hacker";
console.log(pppp);
console.log(ppppCopy);
The output values ​​are the same.
Why modifying the value of one object, the other object will also be modified?
Whenever I modify the value of one object, the value of the other object is also modified.
Because you pppp and ppppCopy holds the same reference of job property. Changing at one location will impact another. You can achieve your intended outcome with below code using ES6 spread operator,
let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
const ppppCopy = {
...pppp,
job: { ...pppp.job },
};
With this, updating pppp.job.title will not impact ppppCopy.job.title.
You can also use the traditional way like JSON.parse(JSON.stringify(pppp)), but you need to be more cautious while using this approach as it strips down the function property
In JS, and many other languages, you have data types that store by value, like Number and other primitive types. Some data types stored by reference, like Arrays, Object.
By destructing pppp you just passing the reference to the inner job object, and not duplicating it, so technically its the same job object in pppp and ppppCopy.
Here I added a manipulation to a primitive, and you can see that there is a difference.
let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = {};
({
name: ppppCopy.name,
age: ppppCopy.age,
job: ppppCopy.job
} = pppp);
pppp.job.title = "Hacker";
pppp.age = 123;
console.log(pppp);
console.log(ppppCopy);
Here is another good answer related
The only way is to use JSON.parse(JSON.stringify(pppp));
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = JSON.parse(JSON.stringify(pppp));
pppp.job.title = "Hacker";
console.log(pppp);
console.log(ppppCopy);
Since objects are non-primitive data types javascript makes a reference of the original object when you make a copy using the assignment operator as you have done in your case. In order to avoid shallow copying you can use the spread operator i.e. let copy = { ...original} or you can use the assign method i.e. let copy = Object.assign({}, original) but both of these fail when you have a nested object. In order to deep copy a nested object you need to do it as Edoardo pointed out above but that will fail too when there is a function in your object. Ravindra's method can also be used, but it will be a hassle when you have multiple nested objects.
The best way to do it in my opinion is to use lodash _.cloneDeep() method. You can read more about it here

Creating "shortcut" to manipulate deeply nested object keys

I have a deeply nested object, and throughout my code I often need to manipulate data like this:
var current_index = test_dict['current']['index']
var current_section = test_dict['current']['section']
test_dict[current_section]['data']['words'][current_index]
So you can see when accessing the object, I'm using variables that reference other parts of the object. This has been working great for me, but throughout my code I also need to periodically update current_index and current_section.
Since those variables are only shallow copies/references and not direct shortcuts to those actual values, doing current_index++ increases current_index but not test_dict['current']['index']
In my code test_dict needs to store all of the current information, so I'm trying to figure out how I can update that dictionary directly without typing out a deeply nested path.
I know I can use dot notation, but that wouldn't save me any time since I would have to do something like:
test_dict[test_dict.current.section]['data']['words'][test_dict.current.index]
I know I can also create a reference to let current_index and let current_section at the beginning of the function, but since I have to manipulate that test_dict object in almost every function it would be impractical to define it hundreds of times.
Is there a better way? Should I just create a getCurrentIndex() function and then do this?
test_dict[getCurrentSection()]['data']['words'][getCurrentIndex()]
Since those variables are only shallow copies/references and not direct shortcuts to those actual values, doing current_index++ increases current_index but not test_dict['current']['index']
OK.. objects(and arrays) are pointers/references.. numbers, strings, booleans, etc are copies.. If you want shortcuts to things, here is an example
var current=test_dict['current'];
var current_index = current['index'];
var current_section = current['section'];
test_dict[current_section]['data']['words'][current_index];
current['index']++ //index inside test_dict would get added to
Perhaps you could try Proxy object:
const dict = {
current: {
index: 1,
section: "test1"
},
test1: {
data: {
words: ["test1 index0", "test1 index1", "test1 index2"]
}
},
test2: {
data: {
words: ["test2 index0", "test2 index1", "test2 index2"],
other: {
sub1: {
sub1_1: ["some", "text", "blah"],
sub1_2: "single string",
sub1_3: {}
}
}
},
blah: {
sub1: {
sub1_4: 123456
}
}
}
}
const test_dict = new Proxy(dict, {
get: function (target, key, receiver)
{
const data = target[dict.current.section] && target[dict.current.section][key] || target[key];
if (typeof data === 'object' && data !== null && !(data instanceof Array))
return new Proxy(data, this)
return data instanceof Array && data[dict.current.index] || data || target;
}
});
console.log("[data][words]", test_dict['data']['words']);
test_dict.current.index = 2
console.log("[data][words]", test_dict['data']['words']);
test_dict.current.section = "test2"
test_dict.current.index = 0
console.log("[data][words]", test_dict['data']['words']);
console.log("data.words", test_dict.data.words);
console.log("data", test_dict.data);
console.log("sub1", test_dict['data']['other']['sub1']);
console.log("sub1_1", test_dict['data']['other']['sub1']['sub1_1']);
console.log("sub1_2", test_dict['data']['other']['sub1']['sub1_2']);
console.log("sub1_3", test_dict['data']['other']['sub1']['sub1_3']);
console.log("blah.sub1_4", test_dict['blah']['sub1']['sub1_4']);

Delete property from shallow copied object

If I have one object, and shallow copy of it. For example:
var person = {
name: "Foo",
age: 10,
child: {
name: "Matrix"
}
}
var copiedPerson = {...person}
console.log(person, copiedPerson);
If I change person.name = "NewName";, copedPerson will stay intact.
If I change person.age = 8;, copiedPerson will stay intact.
But if I change person.child.name = "Neo";, copiedPerson.name will also through reference to point also to same name "Neo".
Everything about that is clear to me.
But I do not understand, why when I delete person.child;, nothing happens to copiedPerson.child;
In my logic cause both of them changes when I change one of them, now I also expected when one of them is deleted that other one should also be deleted. (Cause they references to same location in memory)
Can someone explain me why this didn't happen and which part I misunderstood?
There is a little gap in the mental model you're having about the references right now. So essentially, you understand that if you change the nested object person.child.name the value in the copiedPerson.child.name would change too.
But at the end of the day, the child property is only containing the reference of the nested object for both person and the copiedPerson objects.
So, when you delete it from the original person object, you're deleting the reference of this nested object from the person's child property but this nested object still remains in the memory
Hence, the reference contained in the copiedPerson.child still remains untouched and can access this object saved in the memory
delete does not destroy the object that is referenced by a variable or property.
delete removes a property from an object - in your case the person object - just like an assignment adds it.
it is cause the different variables specify on the same object. You should clone obj by cloneObj function
function cloneObj(obj) {
var res = {};
for (var i in obj) {
if (typeof obj[i] == "object") {
res[i] = cloneObj(obj[i]);
} else {
res[i] = obj[i];
}
}
return res;
}
var person = {
name: "Foo",
age: 10,
child: {
name: "Matrix"
}
}
var copiedPerson = cloneObj(person);
copiedPerson.child.name = "Neo";
console.log(person);
console.log(copiedPerson);

Duplication of items in a Javascript Set

I was going through basics of Javscript Set.According to its defintion, A Set is a special type collection – “set of values” (without keys), where each value may occur only once.
But I see when it comes to reference types the behavior is different. Consider the following snippet:
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
console.log("Scenario 1");
for (let user of set) {
console.log(user.name);
}
let set1 = new Set();
set1.add({ name: "John" });
set1.add({ name: "Pete" });
set1.add({ name: "Mary" });
set1.add({ name: "John" });
console.log("Scenario 2");
for (let user of set1) {
console.log(user.name);
}
I see in the scenario 1, it wont allow duplicates to be added as they are the same references. But in scenario 2 I see duplicates are being added.
Can some explain this behavior? Or Am I missing something.
How scenario 1 is different from 2?
Try doing a check of {name: 'John'} === {name: 'John'}. You would find it returns false.
Every new object has a different reference even though the contents can be same. If it gives false to you, the Set would consider it a different element too.
When you assign a variable with a Reference value, its memory location gets copied.
For example:
let john = {name: 'John'} // lets say memory: XYZ
So, every time when you do: set.add(john);, you are adding the memory location in the set. So, the Set would see you adding XYZ everytime and it won't accept duplicates.
In the second case,
When you do:
`set1.add({ name: "John" });` // You added maybe XYF
`set1.add({ name: "John" });` // You added maybe XYN
So, your Set treats them differently and adds both of them.
A Set does not look at the contents of the object itself. It only looks at the pointer to the object.
If it's not a pointer to the same physical object, then it's allowed to be added to the Set as a different physical object. In fact, you can add an object to the set and then change it's contents afterwards because the fact that it's in the set has NOTHING to do with the contents of the object, only the physical pointer to the object. If it's not the same pointer to the same object, then it can be added separately.
Here's an example:
let s = new Set();
let x1 = {name: "John"};
let x2 = {name: "John"};
console.log(x1 === x2); // false, not the same physical object
s.add(x1);
console.log(s.has(x1)); // true
console.log(s.has(x2)); // false
s.add(x2);
console.log(s.has(x2)); // true
console.log(Array.from(s)); // [{name: "John"}, {name: "John"}];
// now modify x1
x1.name = "Bob";
// the x1 object is still in the set, even though you modified it
// because being in the set has NOTHING to do with the contents of the object at all
console.log(s.has(x1)); // true
console.log(Array.from(s)); // [{name: "Bob"}, {name: "John"}];

Design pattern to check if a JavaScript object has changed

I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).

Categories

Resources