js 메모리와 데이터

남윤하·2024년 2월 9일

JS

목록 보기
8/9
post-thumbnail

데이터 타입

  • 데이터 타입은 기본형과 참조형이 있다. 기본형과 참조형의 구분은 값의 저장 방식과 불변성 여부이다. (기본형은 불변성을 띄지만 참조형은 불변성을 띄지 않는다.)

🦾 메모리와 데이터

메모리

개념

  • 모든 데이터는 메모리(byte 단위로 구성된 식별자) 주소값을 통해서 서로 구분된다.
  • byte는 8개의 비트로 구성된다.
  • 비트는 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 조각 (컴퓨터가 이해할수 있는 가장 작은 단위이다.) > 이 조각들이 모여 메모리 형성

예시

  • 64비트(8바이트) 정수가 메모리에 저장되기 위해서는, 메모리에서 8개의 연속된 바이트에 저장된다.

🔗변수 선언과 데이터 할당

할당 방식

  • 선언부인 변수 영역과, 할당할 때의 데이터 영역은 별개의 주소로 값이 저장된다. (변수따로 값따로 다른 공간에 저장)
/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';

/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
  • 변수 영역과 데이터 영역으로 주소값이 나뉘어지고, 변수의 주소 1002에서 데이터 영역의 주소인 "test!"라는 값이 할당된 5002 주소를 참조하고 있다.

값을 변수에 바로 할당하지 않는 이유

1. 자유로운 데이터 변환

🎈 값을 한 번에 저장할 때:

  • 데이터 크기가 동적으로 변경될 때 메모리에 할당된 공간이 늘어난다. 이 때 메모리에 할당된 영역이 옆으로 밀릴 수 있다.

🎈 참조할 때:

  • 참조 방식에서는 값이 저장된 메모리 주소를 변수가 참조.
  • 변수가 참조하는 것은 메모리 주소이며, 주소의 크기는 고정되어 있다.
  • 데이터 크기가 늘어나더라도 변수가 참조하는 메모리 주소는 변하지 않는다.

🎈 참조 방식의 이점:

  • 참조 방식에서는 값이 저장된 메모리 주소만을 변수가 갖기 때문에, 데이터 크기의 변화에 영향을 받지 않는다.
  • 데이터 크기가 동적으로 변경될 때에도 변수의 크기는 변하지 않는다.
  • 이로 인해 메모리 공간을 효율적으로 활용할 수 있다.

🎈 참조 방식의 선택 이유:

  • 데이터의 크기가 동적으로 변할 때마다 변수의 크기를 조절하는 것의 번거러움.
  • 참조 방식을 사용하면 주소만을 변수가 갖게 되어, 데이터의 크기 변화에 유연하게 대응할 수 있다.
  • 따라서 참조를 통해 저장하든, 값을 한 번에 저장하든 데이터 크기의 동적 변화에 대한 영향을 갖지만, 참조 방식을 사용하면 이에 대한 효율적인 대응이 가능하다는 것이 중요한 이유 중 하나이다.

2. 메모리의 효율적 관리

  • 1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당하는 상황을 가정해 볼 때, 모든 변수를 별개로 인식한다고 한다면, 1만개의 변수 공간을 확보해야 한다.
  • 변수 영역에 저장되는 데이터를 2byte라고 가정했을 때

🏸 참조

  • 10000개의 주소에 변수를 할당 하고 각 주소에 데이터 주소를 참조하게 된다면 변수영역이 차지하는 데이터는 20000바이트, 데이터 영역이 참조하는 값은 8bte(숫자는 8bye로 고정)이기 때문에 총 20008바이트가 차지된다.

🏸 참조x

  • 변수와 데이터를 한번에 저장할 경우 10000개의 주소에숫자 데이터가 할당되기 때문에 총 80000만 바이트가 차지된다.

🥼기본형 데이터와 참조형 데이터

  • 변수와 상수를 구분하는 영역은 변수 영역이며 가변하다 불변하다를 나누는 기준은 데이터 영역이다.

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

// a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봅시다!

// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정할게요.
var a = 'abc';

// 'def'라는 값이 @5002라는 주소에 추가되는 것이 아니죠!
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있습니다.
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됩니다.
a = a + 'def';

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

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

  • 기본형 데이터의 변수 할당 과정과 차이점 : 객체의 변수(프로퍼티) 영역의 별도 존재 여부 (위의 사진에선 "7103~" 주소)

😱 불변성을 띄지 않는다.

  • 데이터 변경할 때 알 수 있다.
var obj1 = {
	a: 1,
	b: 'bbb',
};

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

  • 데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경이 가능하다.
  • 이것 때문에 참조형 데이터를 흔히, ‘불변하지 않다(=가변하다)’라고 한다.

👓 중첩객체의 할당

  • 중첩 객체는 객체안에 또 다른 객체가 들어가는 것을 말한다.
    (객체는 배열, 함수 등을 모두 포함하는 상위개념이기 때문에 배열을 포함하는 객체도 중첩객체라고 할 수 있다.)
var obj = {
	x: 3,
	arr: [3, 4, 5],
}

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

🧨 참고

  • 참조카운트
    객체를 참조하는 변수나 다른 객체의 수를 나타내는 값이다. 참조 카운트가 0인 객체는 더 이상 사용되지 않으므로, 가비지 컬렉터에 의해 메모리에서 제거된다.

  • 가비지컬렉터(GC, Garbage Collector)
    더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할을 한다. 자바스크립트는 가비지 컬렉션을 수행함으로써 개발자가 명시적으로 메모리 관리를 하지 않아도 되도록 지원한다. 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없습니다.

변수복사 비교하기

🍳복사하기

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

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

😨복사 이후 값 변경(객체의 프로퍼티 변경)

  • 기본형과 참조형의 두드러지는 차이는 복사한 후의 값 변경에서 일어난다.
// STEP01. 쭉 선언을 먼저 해볼께요.
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형

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

b = 15;
obj2.c = 20;

  • 변수 b의 경우에는 데이터 영역에 추가된 15값의 주소로 참조값을 변경하기만 하면 되지만, obj2의 경우에는 obj1 영역의 주소를 그대로 참조하고 있는 상태에서 속성의 값이 참조하고 있는 데이터 주소를 변경하기 때문에 obj1도 동시에 영향을 받게 된다.
// 기본형 변수 복사의 결과는 다른 값!
a !== b; // true

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

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

  • 이러한 경우 객체의 프로퍼티에 접근해서 값을 변경하는 것이 아니라 객체 자체를 변경하는 방식으로 바꾸면 된다.
//기본형 데이터
var a = 10;
var b = a;

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

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

  • 이렇게 새롭게 객체에 자체를 변경하게 되면 새로운 주소값을 참조하게 된다.

  • 위 예시처럼 참조형 데이터가 ‘가변값’이라고 할 때의 ‘가변’은 참조형 데이터 자체를 변경할 경우가 아니라, 그 내부의 프로퍼티를 변경할 때 성립한다고 할 수 있음.

🙄 불변 객체의 필요성

문제확인

  • 아래의 코드에서 객체의 가변성에 따른 문제을 확인해볼 수 있다.
// 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

해결방법 1.직접 입력

// 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 👍

이렇게 가변한 것들을 불변한 것처럼 해주어야 한다.

해결방법 2. 얕은 복사

  • 해결방법 1처럼 changeName 함수가 새로운 객체를 만들기 위해 변경할 필요가 없는 gender 프로퍼티를 하드코딩으로 입력했을 때, 필요한 속성이 여러개라면 많은 어려움을 겪는다.

  • 얕은 복사를 이용했을 때, 위의 방법을 더 개선시킬 수 있다.

패턴

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

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

  • 위의 매개변수 target은 객체이다.
    for... in 문을 통해서 객체의 키들을 전부 뒤져서 속성들을 복사해줄 수 있다.

적용

//위 패턴을 우리 예제에 적용해봅시다.
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);

  • 이것을 얕은 복사라고 한다.

해결 방법 3. 깊은 복사

  • 그렇지만 이러한 얕은 객체는 중첩된 객체를 커버해주지 못한다. 객체안에 객체는 여전히 주소값을 그대로 가져온다.
  • 이를 개선시키기 위해선 깊은 복사를 해야 한다.
    1. 얕은 복사 : 바로 아래 단계의 값만 복사(위의 예제)
      문제점 : 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때, 주소값만 복사
    1. 깊은 복사 : 내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법
  • 중첩된 객체에 대한 얕은 복사 살펴보기
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를 copy할 때, 새로운 데이터 영역에 주소를 할당하지만, 그 안에서 참조하고 있는 주소의 영역은 그대로 이기 때문에, 여전히 문제가 존재한다.

  • 결국, user의 프로퍼티들도 불변 객체로 만들어야 한다

  • 1depth뿐만 아니라 그 이상의 깊이마저도 새로운 주소값을 만들어 객체에 전달해줄 수 있는 방법이 필요

  • 중첩된 객체에 대한 깊은 복사 살펴보기

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

// 1차 copy
var user2 = copyObject(user);

// 2차 copy -> 이렇게까지 해줘야만 해요..!!
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);

  • 결론 : 객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사 ⇒ 재귀적 수행

해결방법 4. 재귀적 수행

  • 재귀적으로 수행한다?
    ⇒함수나 알고리즘이 자기 자신을 호출하여 반복적으로 실행되는 것을 말한다😎
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;
}
  • 이렇게 되면, 우리가 그토록 원하던 ‘깊은 복사’를 완벽하게 구현할 수 있다.
//결과 확인
var obj = {
	a: 1,
	b: {
		c: null,
		d: [1, 2],
	}
};
var obj2 = copyObjectDeep(obj);

obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;

console.log(obj);
console.log(obj2);

정리

  • 불변 유지방법에는 얕은복사, 깊은복사가 있는데, 깊은 복사를 할 때에는 재귀적 수행을 하는 방법이 가장 좋다.
profile
개발 일지 블로그

1개의 댓글

comment-user-thumbnail
2024년 2월 10일

설명이 맛있네요

답글 달기