javascriptshallow-copy

Shallow Copy, Map and Spread Operator


I am trying to understand how shallow copy works with map and without map on an array.

const arr = [
 {
   name: 'example',
   inner: {
     title: 'test'
   }
 }

]
const copy = arr.map(a => ({
  ...a
}))

copy[0].name = 'name1'
copy[0].inner.title = 'title1'

Updating name prop on the copy will not affect the original array arr. However, updating inner.title will modify both copy and arr. As far as I understand, in a shallow copy only top/first level props are copied, nested objects still share the same reference.

However, now if I create a shallow copy without using map then even the first/top level props will share the same reference I think or at least updating first/top level props on the copy will affect the original one.

// same arr above

const copy2 = [...arr];
copy2[0].name = 'name2';
copy2[0].inner.title = 'title2';

Now, the original arr's name value is 'name2' and inner.title value is 'title2'. Why is that? What is the exact difference between two code snippets, how map plays a role in shallow copy?


Solution

  • It's easiest to start with your copy2 as that's simpler. When you do this:

    const copy2 = [...arr];
    

    that is use the "spread" operator on an array, you make exactly a "shallow copy" of the array. This means that copy2 and arr are references to different arrays (arrays that are in different memory locations, if you want to think of it that way, although memory management is just an implementation detail of the JS engine rather than something JS developers have to think about), but their contents, if they are objects (which they are here) are still referencing the same things. That is arr[0] and copy2[0] are literally the same object - in the same memory location, if you want to think of it that way - and mutating one will mutate the other just the same (as your code snippet and its result proves).

    copy though is doing something rather different:

    const copy = arr.map(a => ({
      ...a
    }))
    

    While you're also using the "spread" operator to here to again make "shallow copies", what it is that you're copying is different. It's not the array you're copying - it's a, which stands for an element of the array. (This affects all elements because that's what map does.) So what you're doing here is making a new array called copy, that isn't a reference to any previously existing array, whose elements are shallow copies of the corresponding elements in a. Which is why changing copy[0].name doesn't affect arr[0].name, because those 2 objects are references to different things - due to the shallow copy.

    TLDR: the key difference is that in copy2 it's the whole array that you've made a shallow copy of, and because it's shallow, mutations to its object elements mutate the elements of the original. Whereas with copy you've actually shallowly copied each individual element in in, thereby going "one level deeper" if you like.