Primitive typed values are copied by values, whereas objects typed values are copied by reference. In your second example both properties are primitive. that's why it's a deep copy (values were copied), but in the first example, the property is an object; hence it's a shallow copy (reference were copied).
Spread Operator
First you should understand how the spread operator really works. If the oldObj is printed as it's, then as you see the array is printed as the value assigned to it, but if you put oldObj inside of an object, then it's printed as '{oldObj: '}.
Now let's use the spread operator to oldObj while using it inside of an object, then as you see the key:value pair was taken out, and inserted into the outside object.
So this is what spread operator does. It's quite easy to understand it with arrays than objects. It removes elements from inside array, object, and insert them into the outside array, object. In array, if you use ...[1,2,3], it comes as 1,2,3, meaning elements were all removed from the array, and available as individual elements. With objects, you can't use as ...{key:'value'} but you can use as {...{key:'value'}} as then the key:value pair is inserted into the outer object ( {key:'value'} ) after removing from the inner object.
//Example 1
const oldObj = {a: {b: 10}};
console.log(oldObj); // { a: { b: 2 } }
console.log({oldObj}); // { oldObj: { a: { ... } } }
console.log({...oldObj}); // { a: { b: 2 } }
Your Example
In the second example, as I mentioned earlier, oldObj key:value pair was removed from the inner object, and included in the outer object; hence you get the same object.
{ oldObj : {a: {b: 10}} } -> {a: {b: 10}} // (with spread operator)
The thing in here is a: has an object typed value; hence it's copied by REFERENCE, meaning when you copy oldObj to newObj, you copy the reference. That's why when you change oldObj.a.b, it affects to the newObj too.
//Example 2
const oldObj = {a: {b: 10}};
const newObj = {...oldObj};
oldObj.a.b = 2;
console.log(newObj) //{a: {b: 2}}
console.log(oldObj) //{a: {b: 2}}
In the third example, both the values of the a, b properties are primitive types. When primitive typed values are copied, you literally make a deep copy (copy values not reference). When you made a deep copy, and make a change to the old value, it doesn't affect to the new one, because both these properties are located in different locations of the memory.
//Example 3
const oldWeirdObj = {a:5,b:3};
const newWeirdObj = {...oldWeirdObj};
oldWeirdObj.a=2;
console.log(oldWeirdObj) //{a:2,b:3}
console.log(newWeirdObj) //{a:5,b:3}
If you want to copy by VALUES, then you have to copy each values instead of objects, as following. What happens in this example is, you assign {a: {b: 10}} to oldObj constant, then If you want to referer to property 'a' to access its value, you have to use as 'oldObj.a'. So you use the spread operator to it to take its value out, which is a primitive data typed value (10), then you use {a : X } as this to make a new object.
Basically it goes like this a : {...{b: 10}} -> a : {b: 10}, meaning you get the same object with a deep copy. Now if you make a change to oldObj.a.b, it only affects to the old object, not the new one.
//Example 4
const oldObj = {a: {b: 10}};
const newObj = {a: {...oldObj.a}};
oldObj.a.b = 2;
console.log(newObj) //{a: {b: 10}}
console.log(oldObj) //{a: {b: 2}}
primitive
value vsreference
value developer.mozilla.org/en-US/docs/Web/JavaScript/… – Banjermasin