일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 항해 플러스 프론트엔드
- rust
- 성능최적화
- 항해99
- 개발공부
- 리뷰
- 알고리즘
- 프론트엔드
- wil
- 자바스크립트
- naver
- 개발자
- 보안
- 개발 공부
- 회고
- 테스트 코드
- 분기 회고
- React Query
- 성장일지
- React
- 항해플러스
- 항해
- 백준
- frontend
- javascript
- 항해 플러스
- GPU
- webGPU
- FE
- typescript
- Today
- Total
느릿늘있
참조값의 깊이에 대한 고찰 (feat. 복사와 비교) 본문
항해 플러스를 진행하면서 메모이제이션을 구현해보는 미션을 받았습니다. 객체를 비교함에 있어서도 얕은 복사(Shallow Copy), 깊은 복사(Deep Copy)와 마찬가지로 얕은 비교(Sallow Equals), 깊은 비교(Deep Equals)라는 용어를 사용한다는 것도 이번 과제을 진행하면서 처음 알게 되었는데요. 그런데 두 개념을 이해함에 있어서 깊이(Depth)라는 키워드가 약간 다르게 쓰이는 것 같아서 이번 기회에 깔끔하게 정리해보겠습니다.
보통 개발을 바닥부터 시작하게 되면 저처럼 얕은 복사와 깊은 복사에 대해서 먼저 접하게 되는데요. 제 기준으로는 비교에 대해서 먼저 짚고 넘어가는 게 이해하기가 더 수월하다고 생각이 듭니다! 그래서 비교에 대해서 먼저 정의를 해보겠습니다. 정말 간단한데요. 얕은 비교는 객체에서 한 depth만 비교하는 거고 깊은 비교는 참조값이 중첩되어 있을 때, 비교 가능한 원시값이 나올 때까지 재귀해 들어가서 비교하는 것입니다. 얕은 비교에 대해서 좀 더 설명하자면 원시값에 대해서는 그냥 같은 지 비교를 하면되고 참조값에 대해서는 한 depth만 비교하기 때문에 주소가 같은지 체크합니다.(주소가 같으면 같은 데이터) 그러니 내부에 값이 같더라도 참조하는 주소가 다르면 다르다고 판단하는 것이죠! 이는 한 depth 까지만 체크한다라는 얕은 비교의 기조를 생각하면 쉽게 이해되실 겁니다.
object !== { ...object }
// Shallow Equals 구현 예시
export function shallowEquals(targetA: any, targetB: any): boolean {
// ES6 문법인 Object.is로 비교해야 정확하게 체크된다. (그냥 외워!ㅋ_ㅋ)
if (Object.is(targetA, targetB)) return true;
// A와 B가 객체가 아닌 경우 + null인 경우 체크 (null의 타입은 "object"라서 이렇게 따로 체크)
if (typeof targetA !== 'object' || targetA === null ||
typeof targetB !== 'object' || targetB === null) {
return false;
}
// 배열의 타입은 "object"라서 따로 체크
const isArrayA = Array.isArray(targetA);
const isArrayB = Array.isArray(targetB);
// 둘 다 배열이 아닌 경우 같을 수가 없음
if (isArrayA !== isArrayB) return false;
if (isArrayA && isArrayB) {
// 둘 다 배열인데 길이가 다르면 같을 수가 없음
if (targetA.length !== targetB.length) return false;
// 요소 하나하나가 같은 지 체크 ===(동등 연산자) 대신 Object.is를 쓰자! (그냥 외워!ㅋ_ㅋ)
return targetA.every((value, index) => Object.is(value, targetB[index]))
}
// 둘 다 객체인 경우밖에 안남았음!
const keysA = Object.keys(targetA);
const keysB = Object.keys(targetB);
// 길이가 다르면 같을 수 없음
if (keysA.length !== keysB.length) return false;
// 각 key가 같은 지 확인 마찬가지로 Object.is로 비교!
return keysA.every(key => Object.is(targetA[key], targetB[key]));
}
얕은 복사와 깊은 복사는 다들 잘 아실거라 생각해서 간단하게 설명하겠습니다. 얕은 복사는 참조값에 대해 주소를 복사해서 같은 주소를 바라보도록 하는 것이고 깊은 복사는 내부의 값을 하나하나 복사해서 새로운 참조값으로 생성하는 것입니다. 참고로 depth(깊이)가 없는 원시값에 대해서는 적용할 수 없는 개념이니 헷갈리면 안됩니다!
제가 헷갈렸던 부분은 A를 얕은 복사를 한 값을 B라고 할 때, A와 B를 같다라고 판단하는 것을 얕은 비교라고 생각하고 접근했던 부분입니다. 즉, 얕은 비교를 "A의 주소와 B의 주소가 같냐" 라고만 생각한 것이죠. 하지만 중요한 포인트는 A와 B의 주소가 다르더라도 한 depth 안에 있는 참조값의 주소가 동일하다면 같다라고 판단하는 부분입니다.
또 하나 헷갈릴만한 포인트는 "얕은 복사한 객체는 얕은 비교했을 때 참이고 깊은 복사한 객체는 깊은 비교했을 때 참인가?" 입니다. 이게 참 쉬운데 설명하자면 말이 길어지는 부분이라 표로 우선 정리했습니다.
얕은 비교 | 깊은 비교 | |
얕은 복사 | true | true |
깊은 복사 | false | true |
우선 깊은 비교는 참조에 대해 중요하게 생각하지 않고 재귀해서 들어가기 때문에 안에 있는 원시값들이 같냐만 판단합니다. 그렇기 때문에 얕든 깊든 복사하면 깊은 비교에서는 같다고 판단합니다. 하지만 얕은 비교는 객체 안의 참조값(객체)의 주소가 다르면 다르다라고 판단하기 때문에 주소만 참조하는 얕은 복사의 경우에는 같다고 보지만 내부의 값을 그대로 복사해서 새로운 객체를 반환하는 깊은 복사는 다르다라고 판단하는 것입니다. 이를 코드로 표현하면 아래와 같습니다.
const objA = { first: 1, second: 2 };
const objB = { ...objA };
Object.is(objA, objB); // false
얕은 비교의 소스 코드를 보면 마지막 줄에서 객체 데이터를 비교할 때 Object.is로 비교하고 있는 것을 볼 수 있습니다. 따라서 깊이가 있는 객체를 깊은 복사를 하게 되면 얕은 비교에서 false를 반환한다는 이 개념을 꼭 기억해주세요!
'개발공부' 카테고리의 다른 글
[TypeScript] 포스텔의 법칙 In TypeScript (w. 이펙티브 타입스크립트) (0) | 2024.12.17 |
---|---|
[React Hooks] useContext 이해하기 (0) | 2024.10.13 |
브라우저 웹 스토리지와 인증 (0) | 2024.09.18 |
우당탕탕 RUST 도전기 (2) (0) | 2024.09.18 |
우당탕탕 RUST 도전기 (1) (0) | 2024.06.23 |