티스토리 뷰

TIL

TIL 221206 얕은 복사 깊은 복사

2021bong 2022. 12. 6. 22:53

얕은 복사와 깊은 복사를 주소를 참조하는지, 데이터를 복사하는지로 알고 있었다. 그래서 복사를 하고 싶을 때 깊은 복사를 해서 잘 사용하고 있었는데 오늘 깊은 복사를 했는데도 얕은 복사를 한 것처럼 작동하는 상황을 만나 새로운 사실을 알게 됐다.

 

얕은 복사(shallow copy)

얕은 복사는 해당 요소를 참조하는 데이터 주소를 할당하게 된다. 그래서 얕은 복사를 한 요소를 변경하면 원본 요소가 변경된다. ===를 사용해서 비교해봐도 똑같다고 나온다.

const original = { name: 'original' };
const copy = original;

console.log(original.name); // original
console.log(copy.name); // original

copy.age = '40';

console.log(original); // { name: 'original', age: '40' }
console.log(copy); // { name: 'original', age: '40' }

console.log(original === copy); // true

 

깊은 복사(deep copy)

깊은 복사는 해당 요소의 데이터를 복사해 새로운 메모리에 할당하는 것이다. 그래서 깊은 복사를 한 요소를 변경해도 원본 요소는 변하지 않는다. ===를 사용해서 비교하면 false가 나온다.

const original = { name: 'original' };
const copy = { ...original };

console.log(original.name); // original
console.log(copy.name); // original

copy.age = '40';

console.log(original); // { name: 'original' }
console.log(copy); // { name: 'original', age: '40' }

console.log(original === copy); // false

 

깊은 복사를 하는 방법 중에 하나로 스프레드 문법이 있다. 그동안 나는 스프레드 문법을 알게 된 뒤 원본 배열이 변경돼서 곤란했던 적이 없었다. 리액트의 불변성을 지킬 때도 아주 잘 썼고 여태 곤란했던 적이 없는데 오늘 예상치 못한 상황을 만났다...

const original = { name: 'original' };
const copy = { ...original };

const arr = [1, 2, 3];
const arr2 = [...arr];

console.log(original); // { name: 'original' }
console.log(copy); // { name: 'original' }
console.log(original === copy); // false

console.log(arr); // [ 1, 2, 3 ]
console.log(arr2); // [ 1, 2, 3 ]
console.log(arr === arr2); // false

 

깊은 복사도 1 depth만

프로그래머스 안전지대를 푸는데 인자로 들어온 원본 배열을 복사해서 새 배열의 값을 변경하려는데 자꾸 원본 배열이 바뀌었다. 요소의 값이 1이면 값을 hey로 변경하도록 했을 때

function solution(board) {
  let newBoard = [...board];
  for (let i = 0; i < board.length; i += 1) {
    for (let j = 0; j < board.length; j += 1) {
      if (board[i][j] === 1) {
        newBoard[i][j] = 'hey';
      }
    }
  }
  console.log('board', board);
  console.log('newBoard', newBoard);
}

 

코드를 실행해보면 둘 다 바뀌어있는 것이다.

const case1 = [
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 1, 1, 0],
  [0, 0, 0, 0, 0],
];

solution(case1);

// board [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 'hey', 'hey', 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]
// newBoard [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 'hey', 'hey', 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]

 

이유를 찾아보니 스프레드 문법이나 다른 깊은 복사 방법으로 복사를 해도 1 depth만 깊은 복사가 되고 그 보다 깊은 depth에 대해서는 얕은 복사가 된다고 한다. 그래서 board가 2차원 배열이라서 board[i]까지는 깊은 복사가 됐지만 board[i][j]는 얕은 복사가 돼서 값을 변경하면 원본이 함께 변경되는 것이었다..! 유레카...!!!!!!!!! 🫢

function solution(board) {
  let newBoard = [...board];
  for (let i = 0; i < board.length; i += 1) {
    for (let j = 0; j < board.length; j += 1) {
      if (board[i][j] === 1) {
        newBoard[i][j] = 'hey';
      }
    }
  }
  console.log(board === newBoard);
  console.log(board[0][1] === newBoard[0][1]);
}

solution(case1);

// false -> board === newBoard
// true -> board[0][1] === newBoard[0][1]

 

혹시 map도 그런가 싶어서 해봤더니 같았다.

function solution(board) {
  let newBoard = board.map((el) => el);
  for (let i = 0; i < board.length; i += 1) {
    for (let j = 0; j < board.length; j += 1) {
      if (board[i][j] === 1) {
        newBoard[i][j] = 'hey';
      }
    }
  }
  console.log('board', board);
  console.log('newBoard', newBoard);
}

solution(case1);

// board [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 'hey', 'hey', 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]
// newBoard [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 'hey', 'hey', 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]

 

그래서 2차원 배열에 map을 2번 돌렸더니 원하던 데로 깊은 복사가 되었는지 원본 배열이 변경되지 않았다.

깊은 복사도 1 depth 뿐이라는 사실을 배웠다. 한 번에 깊은 복사를 하는 다른 방법들이 있던데 더 깊은 depth를 가진 요소를 복사해야 될 때 찾아봐야겠다.

function solution(board) {
  let newBoard = board.map((el) => el.map((el) => el));
  for (let i = 0; i < board.length; i += 1) {
    for (let j = 0; j < board.length; j += 1) {
      if (board[i][j] === 1) {
        newBoard[i][j] = 'hey';
      }
    }
  }
  console.log('board', board);
  console.log('newBoard', newBoard);
}

solution(case1);

// board [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 1, 1, 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]
// newBoard [
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 0, 0, 0 ],
//   [ 0, 0, 'hey', 'hey', 0 ],
//   [ 0, 0, 0, 0, 0 ]
// ]

 


옵셔널 체이닝, 배열도 쓸 수 있다..!

옵셔널 체이닝을 그동안 객체에 사용하는 예시만 봤고 그래서 객체에만 사용하고 있었다. 다른 자료형에는 못 쓰고 객체에만 사용할 수 있는 줄 알았는데 알고 보니 배열과 함수에도 사용할 수 있었다. 객체, 배열, 함수에 사용할 수 있다. 따지면 모두 객체니까 객체에만 쓰는 게 맞기는 하다...🤔 새롭게 알게 됐으니 배열에도 옵셔널 체이닝을 잘 활용할 수 있도록 해봐야겠다.

// syntax
obj.val?.prop
obj.val?.[expr]
obj.func?.(args)

 

 

참고

 

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함