본문 바로가기
코딩일기/TIL

TIL-6 데이터 타입(심화)

by 2pro.e_pro 2024. 6. 20.
728x90
반응형

TIL6 데이터 타입(심화)

prologue. 반복되는 개념..

그리고 반복될 수록 심화되는 개념들..

 

끝난줄 알았지? 새로운 개념 하나 더 투~척~!

 

 

1. 데이터 타입 심화

(이미지 출처 : https://velog.io/@imjkim49/자바스크립트-데이터-타입-정리 )

 

JavaScript에서 갑의 타입은 크게 기본형과 참조형으로 구분된다.

구분 기준은 값의 저장 방식과 불변성 여부

아래 기본형과 참조형의 구분 기준을 참고 하면 이해에 도움이 된다.

💡 [기본형과 참조형의 구분 기준]
  1. 복제의 방식
    1. 기본형 : 값이 담긴 주소값을 바로 복제
    2. 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
  2. 불변성의 여부
    1. 기본형 : 불변성을 띔
    2. 참조형 : 불변성을 띄지 않음
자, “불변성을 띈다” 이 말을 이해하기 위해서 우리는 메모리와 데이터에 대한 내용을 이해해야만 합니다. 아래에서 그 배경지식을 낱낱이 살펴보기로 합시다 😎

 

1- 1 메모리와 데이터에 관한 배경지식

이제 우리는 javaScript를 배우며 메모리와 데이터에 대하여 알고 있어야 하는데

이와 관련된 비트, 바이트, 메모리의 개념을 알아보자

 

1) 비트 = 1개

  • 컴퓨터가 이해 할 수 있는 가장 작은 단위
  • 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 조각
  • 이 작은 조각들이 모려서 '메모리'를 구성 할 수 있음

 

2) 바이트 = 비트 8개

  • 0과 1만 표현하는 비트를 모두 찾기에 부담되기에 새로운 단위를 구성
  • 1 바이트는 비트를 8개 구성하고 있음
  • ex) 8바이트는 64비트

 

3) 메모리 = 바이트 단위로 구성

모든 데이터는 바이트 단위의 식별자인 메모리 주소값을 통해서 서로 구분됨

가령, 64비트(8바이트) 정수는 64비트를 8개의 바이트로 분할 하고,
각 바이트를 메모리에 저장해야 한다.

각 바이트는 8개의 비트를 가지므로 64비트 정수는 메모리에서 8개의 연속된 바이트에 저장된다.

 

  • Java, C 언어와는 다른 JavaScript의 메모리 관리방식(정수형)
  • 8을 저장하는 방법
  • JavaScript : let a = 8(8byte)
  • JAVA
    1. byte a = 8(1byte)
    2. short a = 8(2byte)
    3. int a = 8(4byte)
    4. long a = 8(16byte)
  • Java 또는 C 언어가 초기에 등장했을대는 숫자 데이터 타입을 크기에 따라 다양하게 지정해줘야 할 만큼
    개발자가 관리할 요소들이 많았지만 JavaScript는 이런 부분에서 상당히 편리하다.

4) 식별자, 변수

  • var testValue = 3
  • 변수 = 데이터
  • 식별자 = 변수명

1- 2 변수 선언과 데이터 할당

/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';

/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';

 

주소 1002 1003  1004 1005  
데이터   str@5002        
주소 5002 5003 5004 5005
데이터   test!        

 

위와 같이 메모리에 저장될때는 하나의 메모리에 모두 저장되는 것이 아니라

각 데이터 마다 각각의 고유한 값(중복되지 않도록) 값을 저장하게 된다.

값을 바로 변수에 대입하지 않고 무조건 새로 만드는 이유는 아래와 같다.

1) 자유로운 데이터 변환

 

숫자는 항상 8바이트로 고정이지만 문자는 고정이 아니다.(영문 : 1byte, 한글 : 2byte).
그렇기 때문에 1002번 주소에 할당된 데이터를 변환하려고 할때 훨씬 더 큰 데이터를 저장하려 한다면
1003, 1004 이후부터 저장되어있는 모든 데이터를 오른쪽으로 미뤄야 한다.

2) 메모리의 효율적 관리

똑같은 데이터를 여러번 저장해야 한다면 어떻게 될까?
가령 1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당 하는 상황을 가정해 보자면
모든 변수를 변개로 인식해야 하는 상황에서 1만개의 변수 공간을 확보해야 한다.

이 때 바로 대입해야하는 1만개의 1을 1개의 메모리를 담당하는 8바이트에 각각 저장한다면
8만 바이트가 필요하지만 변수영역은 2바이트로 따로 관리 된다고 가정한다면?

2만 바이트가 필요하다. 때문에 변수영역에 각 데이터만 따로관리하여
관리할 데이터 1개와 관리된 변수 1만개로 2만 8바이트로 효율적인 관리가 가능하게 된다.

 

 

1- 3 기본형 데이터와 참조형 데이터

그래서 메모리를 기준으로 다시 한번 두가지 개념을 생각해볼 수 있을텐데,

1) 변수 vs 상수

변수의 경우 변수 영역 메모리를 변경할 수 있지만

상수의 경우 변수 영역 메모리를 변경 할 수 없고,

2) 불변하다 vs 불변하지 않다

그래서

불변하지 않다는 것은 데이터 영역 메모리를 변경 할 수 있는것이고

불변하다는 것은 데이터 영역 메모리를 변경할 수 없는 것이다.

 

3) 불변값과 불변성 (기본형 데이터)

1), 2)의 내용에 이어서 좀더 설명해 보겠다.

 

가령 a 라는 변수가 abc에서 abcdef 가 되는 과정을 통해 불변성을 유추 해본다면

'abc' 값은 @5002번 데이터에 위치해 있을것이고

추가되는 'def'가 @5002번에 끼워지거나 추가되는것이 아니라

@5003에 별도로 'abcdef' 라는 값이 추가 되는것이고

 

@1002번에 있는 a는 더 이상 @5002 번이 아닌 @5003 번의 변수 데이터를 가져오게 될 것이다.

 

즉, @5002 번 데이터가 변하는것이 아닌 @5003의 데이터로 대체 되는 것이기 때문에

@1002 번 데이터에 있는 변수 a는 불변하다 할 수 있는 것이다.

 

이 때 더이상 사용되지 않을 @5002 번의 데이터는 더 이상 사용 되지 않기 때문에 가비지 컬렉터의 수거 대상이 된다.

 

그렇다면 나중에 다시 abc 라는 변수 데이터가 사용되어야 할때는 어떻게 될까?

그때는 @5002 번의 데이터는 이미 사라졌으니 새롭게 @5004 or @5005 번 데이터에 입력되게 될것이다.

주소 1002 1003  1004 1005  
데이터   a @5003        
주소 5002 5003 5004 5005
데이터   abc abcdef      

 

 

 

4) 가변값과 가변성 (참조형 데이터)

// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요하다.
var obj1 = {
	a: 1,
	b: 'bbb,
};

 

만약 위와 같은 데이터가 있다고 할때 참조형 데이터는 어떻게 메모리가 관리되는것일까?

위와 같은 형식의 데이터의 경우 아래 표와 같이 관리 될 수 있다.

주소 1002 1003  1004 1005  
데이터   obj1@7002~7003 obj1.a      
주소 5002 5003 5004 5005
데이터   1 "bbb" 2    

 

주소 7002 7003  7004 7005  
데이터   a @ 5002 <=== @5004로 변경 b@5003      

 

참조형 데이터가 기본형 데이터의 변수 할당 과정과의 차이점이라고 한다면,

객체의 변수(프로퍼티)의 영역이 별도로 존재 할 수 있고 위와 같이 @1002 번의 obj 값이 가리키는 데이터는

@7002, @7003 번 데이터가 되는데 이때 @7002~3 데이터는 각각 @5002~3의 데이터 값을 참조 하고 있는것이다.

 

var obj1 = {
	a: 1,
	b: 'bbb',
};

// 데이터를 변경해봅시다.
obj1.a = 2;

 

가령 obj1.a = 2;라는 데이터가 추가 / 변경된다고 할때(적색표기)

@1003 번 이라는 새로운 obj1.a의 데이터가 입력될 것이고

필요한 출력 값인 숫자 2는 데이터에 지정되어있지 않기 때문에

@5004번 별도 데이터 영역에 추가된다.

 

이로써 obj 라는 데이터 영역은 여전히 불변이고 여전히 @7002번의 데이터를 바라보고 있지만

@7002 번의 데이터는 @5002 번 데이터에서 @5004번의 데이터로 교체가 되었기 때문에

여전히 불변한 것은 맞지만 가변 할 수 있는것이다.

 

 

이번엔 중첩 객체 {} 에 대하여 알아보도록 하자

 

JavaScript에서 중첩 객체 {} 란, 객체 안에 또 다른 객체가 들어가는 말하며,

객체 {}배열 [], 함수 등을 모두 포함하는 상위 개념이기 때문에

배열 [] 을 포함하는 객체도 중첩 객체라고 할 수 있다.

 

var obj = {
	x: 3,
	arr: [3, 4, 5],
}

// obj.arr[1]의 탐색과정은 어떻게 될까요? 작성하신 표에서 한번 찾아가보세요!

 

가령 위 예제와 같은 코드 구문이 있다고 가정할때

아래 표를 통해 obj.arr[1]의 탐색과정을 표현해 보자면

주소 1001 1002 1003  1004 1005  
데이터 obj1@7001~2          
주소 5001 5002 5003 5004 5005
데이터 3 4 5      

 

주소 7001 7002 7003  7004 7005  
데이터 x @5001 arr@8001        

 

주소 8001 8002 8003  8004 8005  
데이터 [0]@5001 [1]@5002 [2]@5003      

 

 

데이터가 위치하는 내용은 사전 개념 설명으로 충분하고,

위 표와 같이  obj.arr[1]의 탐색과정은 결론 적으로 숫자 3이 있는 @5001번으로 출력되는 것이 아니라

각 배열에 대한 인덱스 값인 @8001~3이 담당하게 되는것이고

비로소 @7002 번에 있는 arr 값은 @5001~3 이 아닌  @8001~3에서 각 인덱스 별 값을 찾아내는 것이다.

그래서 배열의  [1]번 인덱스를 포함하는 @8001 의 데이터에서 찾을 수 있게 되는 것이다.

 

 

@1002번에 있는 a는 더 이상 @5002 번이 아닌 @5003 번의 변수 데이터를 가져오게 될 것이다.

 

즉, @5002 번 데이터가 변하는것이 아닌 @5003의 데이터로 대체 되는 것이기 때문에

@1002 번 데이터에 있는 변수 a는 불변하다 할 수 있는 것이다.

 

이 때 더이상 사용되지 않을 @5002 번의 데이터는 더 이상 사용 되지 않기 때문에 가비지 컬렉터의 수거 대상이 된다.

 

그렇다면 나중에 다시 abc 라는 변수 데이터가 사용되어야 할때는 어떻게 될까?

그때는 @5002 번의 데이터는 이미 사라졌으니 새롭게 @5004 or @5005 번 데이터에 입력되게 될것이다.

 

 

4) 변수 복사의 비교

변수 복사의 비교 또한 우리가 지금까지 실습했던 내용과 상당히 유사하다.

먼저 기본형 및 참조형 데이터의 선언과 할당을 해보고 변수 복사까지 진행 해보자.

 

// STEP01. 쭉 선언을 먼저 해볼께요.
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형

// STEP02. 복사를 수행해볼께요.
var b = a; //기본형
var obj2 = obj1; //참조형

 

가령 위 예제와 같은 코드 구문이 있다고 가정할때

아래 표와 같이 데이터가 위치 할 수 있게 되는 것이다.

주소 1001 1002 1003  1004 1005  
데이터 a@5001 obj1@7001~2 b @5001 obj2 @7001~2    
주소 5001 5002 5003 5004 5005
데이터 10 "ddd"        

 

주소 7001 7002 7003  7004 7005  
데이터 c @5001 d@5002        

 

비로소 변수 복사가 완료 된 것이다.

 

 

5) 변수 복사 이후 값을 변경한다면

여기 까지 오느라 복잡할 수 도 있는데 지금부터가  더 중요하다.

기본형과 참조형의 두드러지는 차이는 복사한 후의 값 변경에서 일어나게 되는데,

아래 예제와 함께 알아보자.

 

// STEP01. 쭉 선언을 먼저 해볼께요.
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형

// STEP02. 복사를 수행해볼께요.
var b = a; //기본형
var obj2 = obj1; //참조형

b = 15;
obj2.c = 20;

 

변수 복사 이후 값을 변경 하기전 기본형과 참조형의 변수 복사 시 주요한 절차의 차이점은 다음과 같다.

 

  • 기본형
    • 숫자 15라는 값을 데이터 영역에서 검색 후 없다면 생성
    • 검색한 결과주소 또는 생성한 주소를 변수 영역 b에 갈아끼움
    • a와 b는 서로 다른 데이터 영역의 주소를 바라보고 있기 때문에 영향 없음
  • 참조형
    • 숫자 20이라는 값을 데이터 영역에서 검색 후 없다면 생성
    • 검색한 결과주소 또는 생성한 주소 obj2에게 지정되어 있는 별도 영역(7103~)에 갈아끼움
    • obj1도 똑같은 주소를 바라보고 있기 때문에 obj1까지 변경이 됨
    • 바로 아래와 같은 현상이 생기게 됨.

위 내용을 아래 표에 적용해 보자면

주소 1001 1002 1003  1004 1005  
데이터 a@5001 obj1@7001~2 @5001 obj2 @7001~2    
주소 5001 5002 5003 5004 5005
데이터 10 "ddd" 15 20    

 

주소 7001 7002 7003  7004 7005  
데이터 c @5001 d@5002        

 

위 표처럼 b와 obj2가 바라보는 데이터 내용이 맞지 않게 문제가 생기게 되고 그래서 아래와 같은 결과로 이어진다.

// 기본형 변수 복사의 결과는 다른 값!
a !== b;

// 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과😭)
obj1 === obj2;

 

 

6) 복사 이후 값 변경(객체 자체를 변경)

만약, 갹체의 프로퍼티(속성)에 접근해서 값을 변경하는 것이 아니라

객체 자체를 변경하는 방식으로 값을 바꾼다면 어떨까?

 

//기본형 데이터
var a = 10;
var b = a;

//참조형 데이터
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;
obj2 = { c: 20, d: 'ddd'};

 

앞서 위에서 다룬 내용이므로 표를 통해 알아보기전에

해당 예제서의 추가 된 내용은 b = 15로 변경

추가 되는 obj2의 내용은  = { c: 20, d: 'ddd'};으로 변경하자는 코드구문이다.

 

주소 1001 1002 1003  1004 1005  
데이터 a@5001 obj1@7001~2 b @5003 obj2 @8001~2    
주소 5001 5002 5003 5004 5005
데이터 10 "ddd" 15 20    
주소 7001 7002 7003  7004 7005  …
데이터 c @5001 d@5002        
주소 8001 8002 8003  8004 8005  …
데이터 c@5004 d@5002        

 

그래서 위 표와 같이 파란색 @데이터 주소 값을 추가로 생성하여 

바라보는 데이터 메모리 영역의 값을 변경하게 되는 것이다.

참조형 데이터가 ‘가변값’이라고 할 때의 ‘가변’은 참조형 데이터 자체를 변경할 경우가 아니라,

그 내부의 프로퍼티를 변경할 때 성립한다.

 

2. 불변 객체

우리는 앞선 과정에서 메모리에 값을 대입해 보며 가변하다와 불변하다의 개념을 알아보았다.

 

가령 객체라면 객체의 속성에 접근해서 값을 변경하면 "가변이 성립"

반면, 객체 데이터 자체를 변경 하고자 할땐 "기존 데이터 변경 성립 X" 불변

 

그렇다면 객체 데이터 자체를 변경 하고자 할때 불변불변객체의 개념이 왜 필요한지 알아보자.

 

 

1) 불변 객체의 필요성

다음 예제는 객체의 가변성에 따른 문제점을 보여주고 있다.

// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경했네요! -> 가변
var changeName = function (user, newName) {
	var newUser = user;
	newUser.name = newName;
	return newUser;
};

// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 가변이기 때문에 user1도 영향을 받게 될거에요.
var user2 = changeName(user, 'twojang');

// 결국 아래 로직은 skip하게 될겁니다.
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true

 

그래서 위의 예제를 아래와 같이 개선 할 수 있다.

// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
	return {
		name: newName,
		gender: user.gender,
	};
};

// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 불변이기 때문에 user1은 영향이 없어요!
var user2 = changeName(user, 'twojang');

// 결국 아래 로직이 수행되겠네요.
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍

 

 

그렇지만 아쉽게도 위 방법이 최선은 아니다.

설명을 들어보면 이해하는데 도움이 될 것인데

 

//위 패턴을 우리 예제에 적용해봅시다.
var user = {
    name: 'wonjang',
    gender: 'male',
};

var changeName = function (user, newName) {
    var newUser = user;
    newUser.name = newName;
    return newUser;
};

var user2 = changeName(user);
user2.name = 'twojang';

if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);
console.log(user === user2);

 

 

해당 예제에서의 출력결과를 보자면 user 변수에 선언된 name: 'wonjang', 이 twojang으로

객체가 복사된 것이니라 객체 자체의 값이 변한 것을 알 수 있다.

 

 

그래서 아래와 같이 적용해 볼수 있는데 여기서의 바뀐점을 알아보자.

//위 패턴을 우리 예제에 적용해봅시다.
var user = {
    name: 'wonjang',
    gender: 'male',
};

var changeName = function (user, newName) {
    return {
        name: newName,
        gender: user.gender,
    };
};

var user2 = changeName(user);
user2.name = 'twojang';

if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);
console.log(user === user2);

 

최초 바꾸려고 했던 예제의 문제점은

var changeName = function (user, newName) {
    var newUser = user;
    newUser.name = newName;
    return newUser;
};

 

위와 같이 chageName 함수 안에서 newUser는 user와 같다고 명시하여

user2로 복사해 가려는 user의 속성 자체를 복사해 가는 문제가 있다.

 

user2 에서 twojang으로 변경하면 user의 객체자체가 twojang으로 속성변경이 되고 있음.

 

즉, 위 사진처럼 user2의 속성이 변경되면 기존 선언된 user에서도 속성이 변경된다는 뜻이다.

그래서 아래 변경된 부분과 같이 changeName 함수에서 var선언(user 자체를 복사하던 구문)을 없애고

return 을 통해 user의 객체값 자체를 복사해 오는 방식으로 변경한다면

var changeName = function (user, newName) {
    return {
        name: newName,
        gender: user.gender,
    };
};

 

아래 사진과 같이 user와 user2의 정보가 서로 다르게 복사가 잘 되고

유저정보가 변경되었다는 메세지가 정상적으로 출력되게 된다는 것이다.

 

 

 

 

결국 우리는 특정 코드 구문을 복사하거나 적용할때 가변하다는 성질을 불변하게 끔 변경해주려는 노력이 필요한것이다.

 

그러나..

위와같이 변경하는 방식이 더 나은 방법이라고 할 지라도

객체의 수가 한두개가 아니라 수십개, 아니 수백개 이상이 될 경우에는 어떻게 될까?

 

이렇게 될 경우 다시 하드코딩의 소요에서 벗어날 수 없게 된다.

우리가 지금까지 알아본 더 나은 방법이 좋은 방법인 것은 맞지만 실무에서 적용할 때는

해당 방법이 다시 하드코딩으로 가게 되는 도돌이표 현상이 지속 된다는 것이다.

 

이유는 changeName 함수는 새로운 객체를 만들기 위해 변경할 필요가 없는

gender 프로퍼티를 하드코딩으로 입력했기 때문이다.

 

그래서 위 방법보다는 좀 더 나은 얕은 복사에 대하여 알아보자

 

2) 더 나은 방법 얕은 복사

얕은 복사에 대한 패턴과 적용은 아래와 같다.

 

//이런 패턴은 어떨까요?
var copyObject = function (target) {
	var result = {};

	// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
	// 하드코딩을 하지 않아도 괜찮아요.
	// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
	// 되겠죠!?
	for (var prop in target) {
		result[prop] = target[prop];
	}
	return result;
}

 

for in 문을 활용하여 객체의 모든 프로퍼티에 접근 할 수 있고

이 방식을 적용한다면 하드코딩의 소요가 없어지게 된다.

위 패턴을 예제에 적용해본다면 어떻게 될까?

 

해당 부분은 강조하기 위해 스니펫이 아닌 색깔을 부여해서 알아보자.

 

var copyObject = function (target) {
    var result = {};

    for (var prop in target) {
        result[prop] = target[prop];
    }
    return result;
};

var user = {
    name: 'wonjang',
    gender: 'male',
};

var user2 = copyObject(user);
user2.name = 'twojang';

if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);
console.log(user === user2);

 

 

자 위 처럼 for in 문을 통해 우리가 객체의 각 속성 값을 모두 복사 변경 하지 않아도

for in문 자체에서 모든 속성 값을 확인 해서 다음 복사하려는 객체에서 복사를 해줄 수 있다.

 

복사하려는 대상인 user가 소유하고 있는 객체의 속성 갯수가 몇개가 되더라도

for in 문 하나로 모든 속성을 순회하여 복사 해주기 때문에 코드 자체가 간결해지고 가독성이 높으며

하드 코딩 소요에서도 벗어 날 수 있고, 복사 행위에서도 문제가 없게 끔 이루어 진다는 것이다.

 

 

결과는 당연히 아래 사진과 같이 제대로 된 얕은복사가 된다.

 

 

 

 

하지만..!

 

이 얕은 복사도 여전히 문제는 있다.

왜냐하면 중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문이다.

 

📌중간 정리

  • 얕은 복사 : 바로 아래 단계의 값만 복사 할 수 있다.
    1. 문제점 : 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사 할 때, 주소값만 복사
  • 깊은 복사 : 내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는법(지금부터 알아볼 내용)

3) 얕은 복사 보다 더 나은 방법 깊은 복사

아래 예제에서는 깊은 복사가 중요한 이유를 설명해보려고 한다.

var user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

var user2 = copyObject(user);

user2.name = 'twojang';

// 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라지죠.
console.log(user.name === user2.name); // false

// 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같아요.
// 더 혼란스러워 지는거죠 ㅠㅠ
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true

// 아래 예도 똑같아요.
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

 

얕은 복사는 바로 아래단계(뎁스)의 값만 복사할 수 있기 때문에

user.urls 프로퍼티도 동일하게 복사해주려면 해당 프로퍼티로 불변객체로 만들어야한다.

그렇기 때문에 아래와 같이 코드를 작성해줘야 하며

 

var copyObject = function (target) {
    var result = {};

    for (var prop in target) {
        result[prop] = target[prop];
    }
    return result;
};

var user = {
    name: 'wonjang',
    urls: {
        portfolio: 'http://github.com/abc',
        blog: 'http://blog.com',
        facebook: 'http://facebook.com/abc',
    },
};

var user2 = copyObject(user);

user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);

 

여기서 달라진것은 

 

var user2 = copyObject(user);

user2.urls = copyObject(user.urls);

 

위와 같이 user2에 대한 copyObject에 추가로 user.urls에 대한 복사도 진행해 줘야 하는 점이다.

 

이렇게 해야 비로소 우리가 원하던 "깊은복사"를 완벽하게 구현할 수 있다.

 

📌결론

객체의 프로퍼티 중 기본형 데이터는 그대로 복사하고 참조형 데이터는 다시 그 내부의 프로퍼티를 복사하는데

이것을 재귀적 수행이라고 부른다.

 

추가적으로 JSON(=JavaScript Object Notation)을 이용하는 방법도 존재한다.

JSON(=JavaScript Object Notation) = JavaScript 객체 표기법의 장단점은 아래와 같다.

 

장점
  • JSON.stringify() 함수를 사용하여 객체를 문자열로 변환한 후,
    다시 JSON.parse() 함수를 사용하여 새로운 객체를 생성하기 때문에,
    원본 객체와 복사본 객체가 서로 독립적으로 존재한다.
    따라서 복사본 객체를 수정해도 원본 객체에 영향을 미치지 않는다.

  • JSON을 이용한 깊은 복사는 다른 깊은 복사 방법에 비해 코드가 간결하고 쉽게 이해할 수 있다.
단점
  • JSON을 이용한 깊은 복사는 원본 객체가 가지고 있는 모든 정보를 복사하지 않는다.
    가령, 함수나 undefined와 같은 속성 값은 복사되지 않는다.

  • JSON.stringify() 함수는 순환 참조(Recursive Reference)를 지원하지 않는다.
    따라서 객체 안에 객체가 중첩되어 있는 경우, 이 방법으로는 복사할 수 없다.

 

위 장단점에 따라서 JSON을 이용한 깊은 복사는 객체의 구조가 간단하고,

함수나 undefined와 같은 속성값이 없는 경우에 적합한 방법이다.

만약 객체의 구조가 복잡하거나 순환 참조가 있는 경우에는 다른 깊은 복사 방법을 고려해야 한다.

 

3. undefined와 null

이전 TIL에서도 언급했지만 undefined와 null은 둘다 없음을 나타내는 값이다.

하지만 미세하게 다르고, 그 목적 또한 다르다.

 

아래 정리를 통해 알아보자.

 

1) undefined

사용자(=개발자)가 직접 지정할 수도 있지만 일반적으로는

JavaScript 엔진에서 값이 있어야 할 것 같은데 없는 경우, 자동으로 부여한다.

 

  1. 변수에 값이 지정되지 않은 경우, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
  2. .이나 []로 접근하려 할 때, 해당 데이터가 존재하지 않는 경우
  3. return 문이 없거나 호출되지 않는 함수의 실행 결과

 

var a;
console.log(a); // (1) 값을 대입하지 않은 변수에 접근

var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 property에 접근
// console.log(b); // 오류 발생

var func = function() { };
var c = func(); // (3) 반환 값이 없는 function
console.log(c); // undefined

 

2가지 역할을 가진 undefined 이기 때문에 헷갈릴 수 있고 가끔은 특정상황에서 위험 할 수도 있다.

왜냐하면 특정상황에서 undefined로 출력되는 이 변수가, 필요에 의해 할당한건지

JavaScript 엔진이 반환한건지 구분할 수 없기때문이다.

그래서 ‘없다’를 명시적으로 표현할 때는 undefined 사용을 지양한다.

2) null

사용자(=개발자)가 직접 ‘없다’를 명시적으로 표현할 때 표현한다.

주의할것은 type of null = typeof null이 object인 것은 유명한 javascript 자체 버그가 있으니 주의 해야한다.

 

var n = null;
console.log(typeof n); // object

//동등연산자(equality operator)
console.log(n == undefined); // true
console.log(n == null); // true

//일치연산자(identity operator)
console.log(n === undefined);
console.log(n === null);

 

 

728x90
반응형

댓글