TIL DAY.43 [코어 자바스크립트] 데이터 타입 (2)

Dan·2020년 11월 23일
0

오늘은 저번에 이어서 데이터 타입에 대해서 더 학습하는 시간을 갖도록 하겠다.

데이터 타입 (2)

가변값

참조형 데이터의 프로퍼티에 다시 참조형 데이터를 할당하는 경우인 중첩객체(nested object)에 대해서 알아보도록 해보자.

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

여기서 obj.arr[1]을 검색하고자 하면 메모리에서 어떤 과정을 거치는 지 알아보도록 하자.
1. 먼저 obj라는 식별자를 가진 주소를 변수 영역에서 찾는다.(1002)
2. 해당 변수 영역의 값은 또 다른 주소임으로 그 주소로 이동한다.(5001)
3. 5001는 데이터 영역의 주소이고 해당 값 또한 주소임으로 주소로 이동한다. (7103~?)
4.객체 변수 영역인 7103~?로 이동해 arr이라는 식별자를 가진 주소를 찾는다. (7104)
5. 값이 5003이라는 주소임으로 이동한다. (5003은 데이터 영역)
6. 데이터 영역 5003의 값또한 8104~? 이라는 주소라 이동한다.
7. 8104~?이라는 배열 변수 영역에서 식별자 1를 갖고 있는 주소를 찾는다. (8105)
8. 식별자 1의 주소는 8105이고 값은 5004라는 데이터 영역에 있는 값이라 이동한다. (5004)
9. 데이터 영역에 있는 주소 5004의 값은 4임으로 숫자형 데이터 4를 반환한다.

만약에 위 상태에서 아래와 같이 재할당 명령을 내리면 어떻게 될까?

obj.arr = 'str';

데이터 영역에 빈공간인 5006에 str을 저장하고 그 주소를 위에 arr이라는 식별자를 가진 주소인 7104 값으로 5006을 재할당 시킨다. 그렇게 되면 위에 5부터 9번까지의 과정이 필요가 없어지면서 참조 카운트가 0인 메모리 주소가 된다. 그리고 참조 카운트가 0인 메모리 주소는 가비지 컬렉터 ( garbage collector)의 수거 대상이 된다. 가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때 자동으로 수거되 새로운 값을 할당할 수 있는 빈공간이 된다.

변수 복사 비교

먼저 변수 복사 이후 객체의 프로퍼티를 변경시 어떻게 되는지 예제를 통해서 보도록 하겠다.

var a = 10;
var b= a;
var obj1 = { c: 10 , d:'ddd'};
var obj2 = obj1;
b = 15;
obj2.c = 20;

예제를 보면 변수 영역에서 식별자 a를 빈공간 1001에 값과 함께 저장한다. 식별자 b또한 1002에 a와 같은 값으로 복사하여 저장한다. obj1과 2같은 경우에도 앞과 똑같이 1003과 1004에 저장해준다. 하지만 객체이기 때문에 데이터 영역객체 영역의 주소를 저장해준다. 객체 영역에서 7103~7104를 확보해 사용하게 된다.

기본형 데이터

그 다음 식별자 b 에 해당되는 주소인 1002의 값이 10에서 15로 변경되면서 빈 데이터 영역에 15라는 값을 넣을공간을 만들어 저장해준다. 즉 1002의 변수 영역에서 참조해주는 데이터 값이 변하게 되는것이다.

참조형 데이터

식별자 obj2는 객체 안에 있는 c라는 값만 10에서 20으로 프로퍼티만 변경하게 된다. 그렇게 되면 변수 영역에서는 변화가없지만 객체 영역에서 값을 참조하는 부분에서 변화가 일어난다. 7103이라는 주소에 있는 값이 20으로 변하기 때문이다.

여기서 둘의 큰 차이점이라면

a != b
obj1 === obj2

가 결과 값이라는 것이다. 기본형 데이터 같은 경우는 참조하는 값을 변수 영역에서 바꾼 것이고, 참조형 데이터는 객체 변수 영역에서 참조하는 값이 바뀐것이기 때문에 복사된 obj2가 값을 바꾸면 obj1도 바뀌게 되는것이다.

만약 obj1 != obj2라는 결과값을 나타내고 싶으면 obj2.c = 20처럼 객체의 프로퍼티만 변경하는 것이 아닌 객체 자체를 obj2= { c: 20, d: 'ddd'} 이렇게 변경하게 되면 새로운 객체 변수 영역이 8100~?로 생성이 되면서 obj1 != obj2가 아니게 된다.

즉, 참조형 데이터가 '가변값' 이라고 설명할 때의 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립된다.

불변 객체

불변 객체를 만드는 간단한 방법

불변 객체는 어떤 상황에서 필요할까?
값으로 전달 받은 객체에 변경이 가해지더라도 원본 객체는 변경되지 않아야 할 때가 있다.
다음 두 가지 예시를 보면서 불변 객체가 필요한 경우에 대해서 알아보도록 하겠다.

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

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

var user2 = changeName(user, 'Jung');

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

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

위의 예제는 changeName 함수로 user와 Jung이라는 인자값을 받아와서 user의 name 프로퍼티를 변경하는 식이다. 콘솔의 결과 값을 보면 user.name 과 user2.name이 동일한 것을 볼 수 있다. 그 이유는 객체 자체를 새로 선언해주는 식이 아닌 이미 생성되어 있는 객체 변수 영역안에 있는 프로퍼티의 변화만 줬기 때문이다. 그렇다면 원본은 유지하면서 새로운 user2를 만드는 방법을 살펴보자.

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

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

var user2 = changeName(user, 'Jung');

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

console.log(user.name, user2.name); // hokyun Jung
console.log(user==user2); 	// false

changeName 함수가 새로운 객체를 반환하도록 수정했다. 이제 user 와 user2가 서로 다른 객체이므로 안전하게 변경 전 과 후를 비교할 수 있게 되었다.

얕은 복사와 깊은 복사

얕은 복사

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

var user = {
	
    name : 'hokyun',
    urls : {
    		
            portfolio: 'http//github.com/abc',
            blog: 'http//blog.com',
            facebook: 'http//facebook.com/abc'
            }
 };
 
 var user2 = copyObject(user);
 
 user2.name = 'Jung';
 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.url.blog); //true

위의 얕은 복사 방법의 콘솔을 찍어보면 user이라는 객체안에 있는 name이라는 프로퍼티를 바꿀 경우 원본은 안바뀌지만. user안에 있는 또 다른 객체인 urls안에 있는 내용을 바꾸면 원본도 함께 바뀌는 것을 볼 수 있다.

즉 user객체에 직접 속한 프로퍼티에 대해서는 복사해서 완전히 새로운 데이터가 만들어진 반면, 한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조하는 것이다.

이러한 현상이 발생되지 않기 위해선 user.urls 또한 불변객체로 만들어줘야한다.

깊은 복사

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

var user = {
	
    name : 'hokyun',
    urls : {
    		
            portfolio: 'http//github.com/abc',
            blog: 'http//blog.com',
            facebook: 'http//facebook.com/abc'
            }
 };
 
 var user2 = copyObject(user);'
 user2.urls = copyObject(user.urls);
 
 user2.name = 'Jung';
 console.log(user.name === user2.name); //false
 
 user.urls.portfolio = 'http://portfolio.com';
 console.log(user.urls.portfolio === user2.urls.portfolio); // false
 
 user2.urls.blog = '';
 console.log(user.urls.blog === user2.url.blog); //false

user2.urls = copyObject(user.urls)를 통해 urls프로퍼티 내부까지 복사해서 새로운 객체를 만든다.

즉, 어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사 하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다. 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야한 비로소 깊은 복사가 되는 것이다.

위 개념 바탕으로 깊은 복사 방식으로 코드를 고치면 다음과 같다.

var copyObjectDeep = function(target) {
	var result = {};
    if (typeof target === 'object' && target !== null) {
    	for(var prop in target) {
        	result[prop] = copyObjectDeep(target[prop]);
        }
    } else {
    	result = target;
    }
    return result;
};

3번째 줄에서 target이 객체인 경우 내부 프로퍼티들을 순회하며 copyObjectDeep을 재귀적으로 호출하고 객체가 아닌 경우 8번째 줄에서 target을 그대로 저장하게끔 했다.

제귀함수를 이용한 깊은 복사 결과 확인

var obj = {
	
    a: 1,
    b: {
    	c: null,
        d: [1,2]
        }
 }
 
 var obj2 = copyObjectDeep(obj);
 
 obj2.a = 3; 
 obj2.b.c = 4;
 obj.b.d[1] = 3;
 
 console.log(obj); // {a: 1 , b: {c: null, d:[1,3]}}
 console.log(obj2); // {a :3, b:{c:4, d:{0:1,1:2}}}

undefined 와 null

자바스크립트에는 '없음'을 나타내느 값이 undefined 와 null이 있다. 두 값의 의미는 같은 것 같지만 미세하게 다르고, 사용 목적 또한 다르다.

  1. 사용자가'비어있음'을 명시적으로 나타내고 싶을 때는 null을 쓴다, 애초에 이런 용도로 만든 데이터 타입이다.

  2. 어떤 변수에 값이 존재하지 않을 경우 undefined를 사용한다.

profile
만들고 싶은게 많은 개발자

0개의 댓글