Experience/[Javascript] JS 30

[Javascript] 14. JavaScript References VS Copying

winCow 2021. 4. 25. 16:13

1. Copying

    // start with strings, numbers and booleans
    let age = 100;
    let age2 = age;
    console.log(age, age2);
    age = 200;
    console.log(age, age2);

    let name = 'wes';
    let name2 = name;
    console.log(name, name2);
    name = 'wesley';
    console.log(name, name2);

문자열, 숫자, 불린 자료형을 변수로 할당하면, 해당 값은 메모리에 저장된다. 위 코드에서, age에 100을 할당하고, age2에 age를 할당한 후 콘솔 창에 나타내 보면 100과 100으로 출력될 것이다. 이후, age의 값을 200으로 바꾸고 콘솔 창에 출력해 보면, age는 200으로 바뀌겠지만, age2는 age임에도 불구하고 그 값은 여전히 100이다. 왜냐하면, age2는 age의 값을 참조하는 것이 아니라, 변하기 전의 age의 값을 복사한 것이므로, age의 값이 변한다고 해서 age2의 값이 같이 변하지는 않는다. 문자열을 대상으로 한 name 역시 결과는 같다.

 

 

2. References

    // Let's say we have an array
    const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];

    // and we want to make a copy of it.
    const team = players;

    console.log(players, team);

    // You might think we can just do something like this:
    team[3] = 'Lux';
    console.log(players, team);

반면, 배열이나 객체 등은 변수에 할당해도 해당 데이터가 메모리에 저장되는 것이 아니고, 해당 데이터를 참조하도록 되어 있다. 위와 같이, team이라는 변수에 players 배열을 할당하면, 두 변수, team과 players는 같은 배열을 참조하게 된다. 그러므로, team 배열의 요소를 바꾸면, players 배열의 요소도 바뀌게 된다.

 

 

3. Copying An Array

    // So, how do we fix this? We take a copy instead!
    const players2 = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
    const team2 = players2.slice();
    team2[3] = 'Lux';
    console.log(players2, team2);

이는 다양한 방법으로 해결이 가능한데, 특히, 배열에서는 slice API를 이용할 수 있다. slice(start, end)는 start와 end를 입력하지 않으면 배열을 전부 복사하여 반환한다. 같은 배열을 참조하는 것이 아닌, 배열을 복사하여 반환하므로, team2[3]의 값을 변경해도 players2의 값은 변하지 않는다.

 

    // or create a new array and concat the old one in
    const team3 = [].concat(players2);
    team3[3] = 'Hi-ha';
    console.log(players2, team3);

혹은, 빈 배열을 하나 만든 후, concat으로 원본 배열과 합치는 방법을 이용할 수도 있다. 이렇게 만들어진 team3의 요소를 변경해도, 마찬가지로 players2는 영향을 받지 않는다.

 

    // or use the new ES6 Spread
    const team4 = [...players2];
    team4[3] = 'Holy!';
    console.log(players2, team4);

또는 Spread를 이용해서 players2의 요소들을 그대로 가져올 수 있다.

 

    const team5 = Array.from(players2);
    team5[3] = "Huhehe";
    console.log(players2, team5);

혹은 Array.from()을 통해 복사해 올 수도 있다.

 

 

4. Copying An Object

    // now when we update it, the original one isn't changed
    // The same thing goes for objects, let's say we have a person object
    // with Objects
    const person = {
      name: 'Wes Bos',
      age: 80
    };
    
    // and think we make a copy:
    const captain = person;
    captain.number = 99;
    console.log(person, captain);

이번에는 객체를 복사한다고 가정해 보자. 위 코드에서는, captain이 person을 참조하므로, 콘솔 창을 확인하면 person에도 number: 99라는 데이터가 추가되어 있을 것이다.

 

    // how do we take a copy instead?
    const cap2 = Object.assign({}, person, { number: 69, age: 12 });
    console.log(person, cap2);

Object.assign(target, source) 매소드를 통해 복사를 진행할 수 있다. target은 빈 객체, source는 person 객체와 { number: 69, age: 12 } 객체로 지정한다. assign 매소드는, source로 같은 key를 가진 복수의 객체가 주어진 경우, 더 뒤에 위치한 객체의 값으로 대체한다. 이후, person과 cap2를 콘솔창에 출력하면, person의 number과 age는 변하지 않은 채 80, 99를 유지하고 있을 것이다.

 

 

5. Shallow Copy

위에서 언급한 다섯 가지 방법은 모두 얕은 복사에 해당한다.

    const players3 = ['Wes', 'Sarah', 'Ryan', ['Poppy', 'Boppy']];
    const team2S = players3.slice();
    team2S[3][1] = 'Lux';
    console.log(players3, team2S);


    const team3S = [].concat(players3);
    team3S[3][1] = 'Hi-ha';
    console.log(players3, team3S);


    const team4S = [...players3];
    team4S[3][1] = 'Holy!';
    console.log(players3, team4S);
    

    const team5S = Array.from(players3);
    team5S[3][1] = "Huhehe";
    console.log(players3, team5S);

이를테면, 위와 같이 players3의 마지막 요소를 배열로 바꿔 보자. 그 후, 위의 네 가지 방법을 통해 각각 복사를 한 뒤, players3의 마지막 요소의 마지막 요소를 바꿔 보면, 복사를 진행했음에도 불구하고 players3의 내용이 바뀐다. 즉, 위 네 가지 방법을 사용해 복사를 진행하면, 배열의 요소가 배열이나 객체인 경우에는 이를 참조하게 된다. 

 

객체의 경우에도 마찬가지이다.

    // Things to note - this is only 1 level deep - both for Arrays and Objects. lodash has a cloneDeep method, but you should think twice before using it.
    const wes = {
      name: 'Wes',
      age: 100,
      social: {
        twitter: '@wesbos',
        facebook: 'wesbos.developer'
      }
    }
    const dev = Object.assign({}, wes);
    dev.social.facebook = 'hiwes.developer'
    console.log(wes, dev);

얕은 복사를 하였으므로 wes.social.facebook의 값도 hiwes.developer로 바뀌게 된다.

 

 

6. Deep copy

    const dev2 = JSON.parse(JSON.stringify(wes));
    dev2.social.facebook = 'goodbyeWes.developer'
    console.log(wes, dev2);

JSON.stringify로 해당 객체나 배열을 문자열로 돌린 뒤, JSON.parse로 다시 객체나 배열로 되돌리면 깊은 복사가 진행된다. 이를 통해 중첩 구조를 가진 객체나 배열도 완전하게 복사할 수 있으며, 이에 따라 위 코드에서 wes.social.facebook의 값은 변하지 않는다.