값의 저장 방식과, 불변성 여부에 따라 기본형과 참조형으로 나뉨.
기본형
값이 담긴 주소값을 바로 복제하고, 불변성을 띔.
참조형
값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제, 불변성을 띄지않음
변수명과 데이터는 각각의 주소를 가짐
메모리를 기준으로 다시한번 생각해보는 두 가지 주요 개념
-변수 : 변수 영역 메모리를 변경할 수 있음 (불변하지 않음)
-상수 : 변수 영역 메모리를 변경할 수 없음 (불변하다)
불변값과 불변성(with 기본형 데이터)
var a = 'abc';
a= a + "def";
//a주소값 데이터부분에 abc를 저장한 주소(ex.@5001)가 들어갔다고 가정함.
// a+ "def"가 abc를 저장한주소 @5001에 직접 추가되는게 아님
//@5002에 별도로 "abcdef"가 생기고 이주소가 a주소값 데이터부분에 할당.
//@5001주소는 더이상 사용되지않기 때문에 가비지컬렉터로 수거됨
// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요
var obj1 = {
a: 1,
b: 'bbb,
};

*기본형 데이터의 변수 할당 과정과 차이점 : 객체의 변수(프로퍼티) 영역의 별도 존재 여부
*참조형 데이터가 불변하지 않다(가변하다)라고 하는 이유
var obj1 = {
a: 1,
b: 'bbb',
};
// 데이터를 변경.
obj1.a = 2;

데이터 영역에 저장된 값은 여전히 계속 불변값이지만,
obj1을 위한 별도 영역은 변경이 가능.
이것때문에 참조형 데이터를 불변하지 않다라고 표현.
위의 사진에서 상수냐 변수냐를 구분하는곳은 변수부분.
값이 불변이냐 불변하지않냐를 구분하는곳은 데이터+OBJ를 위한 별도공간
*중첩객체의 할당
var obj = {
x: 3,
arr: [3, 4, 5],
}

*참조 카운트가 0인 메모리 주소처리
참조 카운터 : 객체를 참조하는 변수나 다른 객체의 수를 나타내는 값
0인경우 가비지컬렉터에 의해 메모리에서 제거
가비지 컬렉터 : 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
var b = a; //기본형
var obj2 = obj1; //참조형

var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
var b = a; //기본형
var obj2 = obj1; //참조형
b = 15;
obj2.c = 20;

*기본형
15라는 값이 데이터부분에 없어서 5003에 생성
b에 @5003주소를 할당. a와b는 다른 데이터 영역의 주소를 참조
*참조형
20이라는 값을 데이터부분에서 찾고 없어서 5004에 할당.
obj2의 주소 7103에 값을 5004로 갈아 끼움.
obj1도 주소 7103를 가져오고 있기 때문에 obj1의 c값도 바뀜.
원하지 않았던 결과.
//기본형 데이터
var a = 10;
var b = a;
//참조형 데이터
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;
b = 15;
obj2 = { c: 20, d: 'ddd'};

참조형 데이터가 ‘가변값’이라고 할 때의 가변은
참조형 데이터 자체를 변경할 경우가 아니라,
그 내부의 프로퍼티를 변경할 때 성립
불변 객체의 정의
객체의 속성에 접근해서 값을 변경하는것 가능.(원하지 않는 결과가 나올 수있음.)
객체 데이터 자체를 변경(새로운 데이터를 할당)하고자 한다면
기존 데이터는 변경되지 않음.
불변 객체의 필요성
-객체의 가변성의 문제에 대한 예시
var user = {
name: 'wonjang',
gender: 'male',
};
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
// 함수표현식으로 changeName'을 정의
// 매개변수로 user,newName 받음.
// 새로운 변수newUser 을 선언. 그리고 user값을 할당함.
// newUser.name을 매개변수로 받은 newName로 할당.
// 그리고 newUser을 리턴함.
var user2 = changeName(user, 'twojang');
//user2라는 변수선언. 거기에 changeName함수를 할당하는데
//매개변수에 객체user와 문자열'twojang'을 넣음
//newUser안에 user객채가 들어가게되고
//newUser의 name을 매개변수로 받은 'twojang'으로 교체.
//user2객체의 name속성에 접근해서 값 변경시 그 속성을 가리키고있는 주소값의 데이터는
//user의 name과 같기때문에 user.name도 'twojang'값을 가리키게됨.
// 아래 로직은 skip.
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true
var user = {
name: 'wonjang',
gender: 'male',
};
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
// 함수표현식으로 changeName'을 정의
//user,newName를 매개변수로 받음
//return값으로 새로운 객체를 생성
//그안에 프로퍼티로 name 프로퍼티값으로 매개변수newName를 할당.
//마찬가지로 gender에 매개변수user안의 속성gender의 값을 할당
var user2 = changeName(user, 'twojang');
//user2변수에 changeName을 할당
//새로운 객체 프로퍼티값 name에 twojang을 할당.
//새로운 객체 gender에 user객체의 gender를 할당.
//user과 user2의 프로퍼티네임은 같지만, 그값은 각각의 다른 주소에서 값을 받아옴
//왜? 객체를 새로만들었으니까
// 아래 로직은 실행됨.
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍
단 user속성이 10개 100개라면?
리턴값으로 만들어야하는 속성도 10개 100개만들어야해서 이방법도 완벽한 방법은 아님
var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
//함수표현식으로 copyObject를 만듬.
//빈객체 result를 만들고
//for..in문으로 포인문안에서만 쓸 변수prop에 매개변수 target를 순회하면서 값을 넣어줌
//매개변수target의 키:값들을 result에 넣어줌
var user = {
name: 'wonjang',
gender: 'male',
};
var user2 = copyObject(user);
user2.name = 'twojang';
//user2는 copyObject함수를 실행후 결과값을 할당.
//for..in문으로 상위수준의 속성값, 즉 키와 값만을 복사함. 객체안의 객체나 배열같은경우는
//여전히 참조
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name);
console.log(user === user2);
이방법도 완벽하진않음. 복사하는 객체안에 중첩객체,배열이 있을경우 그값은 여전히 원본과
같은 주소를 참조하게됨.
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',
}
};
// 1차 copy
var user2 = copyObject(user);
//user와 user2의 name는 다른주소를 참조하게됨
// 2차 copy -> 이렇게까지 해줘야만 해요..!!
user2.urls = copyObject(user.urls);
//user의 urls의 값들이 user2의 urls의 값들과 다른주소를 참조하게됨.
//user2의urls를 얕은복사로 참조하는 값들을 바꾸긴 했지만
//만약 user2의 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 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;
}
//함수표현식으로 copyObjectDeep선언
//조건으로 매개변수로 들어오는 target값이 object. 즉 객체나 배열이고 &&앤드연산자로
// null값이 아닌경우만 재귀적으로 객체의 프로퍼티를 복사하게함
//null도 object타입으로 반환하기때문에 따로 필터링 해줘야함
// copyObjectDeep(target[prop])는 매개변수 target의 현재속성값prop를 인자로 받아서
// 자기자신을 호출함.
//객체가 아닌경우 result = target;으로 바로 넣어줌.
//결과 확인
var obj = {
a: 1,
b: {
c: null,
d: [1, 2],
}
};
var obj2 = copyObjectDeep(obj);
//a는 객체아니므로 직접 복사
//b는 객체이므로 b내부의 프로퍼티(c,d)도 복사대상이됨.
//c는 null,객체가 아니므로 직접복사.
//d는 배열이므로 d내부의 프로퍼티도 복사대상이됨.
//d안의 1,2는 객체가 아니므로 직접복사
obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;
console.log(obj);
console.log(obj2);
장점:
원본과 복사본 객체가 독립적으로 존재하여, 복사본 수정이 원본에 영향을 미치지 않음.
코드가 간결하고 이해하기 쉬움.
단점:
함수나 undefined와 같은 일부 속성 값은 복사되지 않음.
순환 참조를 지원하지 않아, 객체 내에 중첩된 객체가 있는 경우 복사할 수 없음.
적합한 경우: 구조가 간단하고, 함수나 undefined 속성이 없는 객체에 대한 깊은 복사가 필요할 때. 복잡하거나 순환 참조가 있는 경우에는 다른 방법을 고려해야 함.