본문 바로가기

Sparta/TIL

23.01.03 누네띠조 3일차 - 불변성

오늘은 JS 3주차 강의를 들었는데, 너무 어려운 부분이었다. 시작은 데이터타입으로 해서 ERD를 작성할 때 어느정도 공부하였기에 다소 잘 이해가 갔다. 하지만 불변성 부분이 나오면서 이해가 힘들어졌다. 그 내용을 정리해보며 복습해보려 한다.

 

* 데이터타입 : 구분 기준은 값의 저장방식불변성 여부

- 기본형(Number, String, Boolean, null, undefined, Symbol) : 원시형이라고도 함.

- 참조형(Array, Function, Date, RegExp, Map/WeakMap, Set/WeakSet) = 객체(Object)

 

=> 구분기준

1. 복제의 방식

1) 기본형 : 값이 담긴 주소값을 바로 복제

2) 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리치는 주소값을 복제

 

2.불변성 여부

1) 기본형 : 불변성을 띔.

2) 참조형 : 불변성을 띄지 않음

 

1. 메모리의 주요 개념

1) 변수 vs 상수

1)) 변수 : 변수 영역의 메모리를 변경할 수 있음

2)) 상수 : 변수 영역의 메모리를 변경할 수 없음

 

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

1))불변하다 : 데이터 영역 메모리를 변경할 수 없음

2)) 불변하지 않다 : 데이터 영역 메모리를 변경할 수 있음

 

 

2. 불변성과 가변성

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

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

*참조형 데이터는 별도 저장공간이 필요합니다!

데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경 가능. = 가변하다.

 

1) 변수 복사의 비교

- 기본형 : 서로 다른 데이터 영역의 주소를 바라보고 있기 때문에 영향없음

- 참조형 : 같은 주소를 바라보고 있기 때문에 같이 변경이 됨.

 

2) 불변객체의 필요성

불변성이 필요한 객체가 가변성을 띠면서 함께 변하게 되면 불필요한 영향을 주고 받는 것이다.

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

 

3) 개선안

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)) 얕은 복사 (for ~ in을 통한 복사) : 바로 아래 단계의 값만 복사

중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때 주소값만 복사

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

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

 

3)) 깊은 복사 (재귀적 수행을 통한 복사, 가장 이상적) : 객체의 프로퍼티 중 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사.

*재귀적 수행 => 함수나 알고리즘이 자기 자신을 호출하며 반복적으로 실행되는 것을 말한다.

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;
}

 

4)) JSON을 통한 복사 : JSON.stringify() 함수를 사용하여 객체를 문자열로 변환한 후, 다시 JSON.parse() 함수를 사용하여 새로운 객체를 생성하기 때문에 독립적으로 존재하여 서로 영향을 끼치지 않는다.

-장점: 코드가 간결하고 쉽게 이해할 수 있다.

-단점: 원본 객체가 가지고 있는 데이터중 함수나 undefined와 같은 속성값은 복사되지 않는다.

그리고 JSON.stringify() 함수는 순환참조를 지원하지 않아 객체가 중첩되어 있는 경우 사용할 수 없다.