티스토리 뷰
얕은 복사와 깊은 복사를 주소를 참조하는지, 데이터를 복사하는지로 알고 있었다. 그래서 복사를 하고 싶을 때 깊은 복사를 해서 잘 사용하고 있었는데 오늘 깊은 복사를 했는데도 얕은 복사를 한 것처럼 작동하는 상황을 만나 새로운 사실을 알게 됐다.
얕은 복사(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)
'TIL' 카테고리의 다른 글
TIL 221227 인앱브라우저 탈출~ (1) | 2022.12.28 |
---|---|
TIL 221221 Cloudinary REST API로 이미지 저장하기 (0) | 2022.12.22 |
TIL 221203 커스텀 훅 만들기(feat. 노마드코더) (0) | 2022.12.04 |
TIL 221130 axios.메소드에 헤더와 바디 넣어 보내기~ (0) | 2022.11.30 |
TIL 221125 Redirect URI가 뭔데(feat. 카카오 로그인) + GitHub 잔디 색상 기준은 뭘까 (0) | 2022.11.25 |
- Total
- Today
- Yesterday
- scss
- javascript
- 코드잇
- 코딩앙마
- 깃
- 파이썬
- 제이쿼리
- Typescript
- React
- 자바스크립트
- TS
- 저스트코드
- CSS
- 리액트
- vscode
- 드림코딩
- 김버그
- 제로초
- 회고
- map
- Python
- git
- Til
- 비주얼스튜디오코드
- js
- vue
- 스파르타코딩클럽
- 타입스크립트
- 구름에듀
- html
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |