TIL 2 - 얕은 복사와 깊은 복사의 이해 :: study

UlongChaS2·2021년 8월 13일
0
post-thumbnail

얕은 복사와 깊은 복사는 데어터 타입 목차에 들어있는 내용이지만 그 전부터 꺼려하던 내용이라 좀 더 자세하게 다뤄보고 싶어 이렇게 나눠 정리해보려
합니다.😊

얕은 복사와 깊은 복사의 차이점

얕은 복사는 바로 아래 단계의 값만 복사하고, 깊은 복사는 내부의 모든 값들을 전부다 복사한다는 차이점이 있다.

이 말은 얕은 복사내부 프로퍼티의 주솟값은 같은 값을 공유하고 있어 내부가 바뀌게 된다면 같이 바뀌게 된다는 것이고, 깊은 복사는 내부 프로퍼티까지 메모리에 값 자체를 할당하여 생성하여 같이 함께 바뀌지 않는다는 점이다.

그래서 얕은 복사를 하게 되면 원본과 사본 중 어느 쪽을 바뀌더라도 다른 한쪽의 값도 함께 바뀌기 때문에 이런 현상을 발생하지 않게 하려면 불변 객체로 만들 필요가 있다.

복사의 종류들

Array.prototype.slice

const arr = [1, 2, [3, 4]];
const copied = arr.slice();

console.log(arr === copied);		// 1️⃣ false
console.log(arr[0] === copied[0]);	// 2️⃣ true

copied[2].push(5);

console.log(arr);	// 3️⃣ [ 1, 2, [ 3, 4, 5 ] ]
console.log(copied);	// 3️⃣ [ 1, 2, [ 3, 4, 5 ] ]

1️⃣ 복사를해서 겉은 다른 객체가 되었지만
2️⃣ 얕은 복사라 내부 프로퍼티는 같은 주솟값을 공유해 true로 나옴
3️⃣ copied에 push를 하였지만 원본과 사본이 같이 바뀌게 됨

Spread Operator

전개 구문이라고 하며 ...를 앞에 붙이면 객체든, 배열이든 그 안에 있는 내용을 복사해준다는 문법이다.

const arr = [1, 2, [3, 4]];
const copied = [...arr];

Spread Operator 역시 얕은 복사이므로 앞서 말한 slice와 같은 방법으로 작동한다.

Object.assign

Object.assign(target, source);형태로 target이 기본값 source가 추가되는 값이 된다.

const arr = [1, 2, [3, 4]];
const copied = Object.assign([], arr);

slice, Spread Operator와 같이 원본과 사본 둘 중 하나를 바꿔도 함께 변하는 결과를 나타냈다.

신기한건 Object.assign의 기본값을 {}로 바꿔보았는데
console.log(arr[0] === copied[0]);이 true로 나왔다는거고 겉이 어떻든 내부 프로퍼티가 값을 공유한다는 사실이 바뀌지 않는다는걸 다시 한번 알고가는 과정이였다.

JSON.parse & JSON.stringify

  • JSON.stringify()
    메소드는 인수로 객체를 받으며 받은 객체는 문자열로 변환한다.
  • JSON.parse()
    메소드는 문자열을 인수로 받으며, 받은 문자열을 객체로 변환한다.

보통 fetch로 데이터를 body안에 JSON.stringify()를 겉에 싸서 백에게 보낸다라고만 알았는데 깊은 복사에도 쓰이게 된다는걸 이제 알았다.

const arr = [1, 2, [3, 4]];
const copied = JSON.parse(JSON.stringify(arr));
console.log(arr === copied);		// false
console.log(arr[0] === copied[0]);	// true

console.log(copied);	// [ 1, 2, [ 3, 4 ] ]

copied[2].push(5);

console.log(arr);	// [ 1, 2, [ 3, 4 ] ]
console.log(copied);	// [ 1, 2, [ 3, 4, 5 ] ]

다른 것은 안되었던 중첩구조 복사도 된다, 문자열로 변경 후 다시 원본 객체로 변경하는 과정에서 자바스크립트의 문자열(string) 이라고 불리는 데이터 타입이 불변성의 형질(immutable primitive type)을 띠는 원시 타입이기 때문이다.

그럼 이것만 사용하면 깊은 복사는 언제든지 사용할 수 있을까?라고 생각하지만 아쉽게도 그렇지 않다.

JSON.parse & JSON.stringify의 아쉬운 점

  • Date 객체를 복사할 수 없다
    JSON.parse 👉 JSON.stringify로 바꾸는 과정에 0.00000001초라도 시간이 흐르기 때문에 같을 수가 없게 된다.
  • BigInt을 처리할 수 없다.
  • 함수 역시 JSON.stringify 가 문자로 바꿀 수 없는 객체 중 하나다.
  • 숨겨진 프로퍼티 __proto__나 getter/setter 등 JSON으로 변경할 수 없는 프로퍼티는 무시한다.

아쉽지만 깊은 복사는 라이브러리를 사용해야할 것 같다

Lodash & Ramda

JSON.parse & JSON.stringify의 아쉬운 점을 없앤 완전하게 깊은 복사를 하는 라이브러리들인데 둘의 동작하는데 다른 점이 많다.

더 자세한 정보는 참고한 사이트에서 들어가서 확인해주시길 바랍니다.

undefined와 null

JavaScript에서 없음을 나타네는 두 값이지만, 사용하는 목적이 다르다.

undefined는 사용자가 지정할 수도 있지만 JavaScript 엔진이 자동으로 부여하는 경우도 있다.

JavaScript 엔진이 undefined를 자동으로 부여하는 경우

  1. 변수가 선언만 되고 값이 할당되지 않았을 때
  2. 객체 내부의 존재하지 않는 프로퍼티에 접근할 때
  3. return 값이 없는 함수를 실행할 때

여기에서 주의할 점은 []{}처럼 비여있는 요소직접 undefined를 할당한 요소는 JavaScript 엔진이 자동으로 부여하는 경우동작하는 흐름이 다 다르다

특히 비여있는 요소와 undefined를 직접 할당한 경우 그 요소를 순회할 때는 일반적으로 알고 있는 것처럼 배열의 모든 요소를 순회해서 결과를 출력하는 반면, 비여있는 요소를 순회하면 어떠한 처리도 없이 건너뛰게된다.

다시 한번 더 흐름만 말하자면 사용자가 명시적으로 undefined를 부여한 경우 어쨋든 비어있음이란 값을 부여해서 하나의 값으로 쳐서 동작하기 때문에 프로퍼티나 배열의 요소로 고유의 키값을 갖게되는 것이고, 자동적으로 undefined는 프로퍼티, 배열의 키값 자체가 존재하지 않는다.

null은 그럼 어쩔 때 쓰는걸까?

자동적으로 JavaScript 엔진이 null은 부여하는 일은 없다. 고로 null이 부여됬다는 것은 사용자가 일부러 '비어있음'을 명시적으로 표현할 때만 사용한다.

null의 주의할 점은 type이 object라는 것이다. 이건 자바스크립트 자체의 버그이기에 변수의 값이 null인지 여부를 판단하기 위해서는 typeof 대신 ===연산자처럼 다른 방법으로 접근해야한다.


참고한 사이트

깊은 복사와 얕은 복사에 대한 심도있는 이야기
얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)


0개의 댓글