불변 객체
TIL 23. 10. 30
오늘 한 일
- 불변 객체
불변 객체
자바스크립트 데이터 타입은 크게 원시 타입과 객체 타입으로 구분할 수 있다. 원시 타입과 객체 타입의 차이는 크게 세 가지가 있다.
- 원시 값: 변경 불가능한 값 ↔ 객체: 변경 가능한 값
- 원시 값을 변수에 할당하면 변수에는 실제 값이 저장 ↔ 객체를 변수에 할당하면 변수에는 참조 값이 저장
- 원시 값은 값에 의한 전달 ↔ 객체는 참조에 의한 전달
원시 값
원시 타입의 값, 즉 원시 값은 변경 불가능한 값이다. 원시 값을 할당한 변수에 새로운 원시 값을 재할당하면 메모리 공간에 저장되어 있는 재할당 이전의 원시 값을 변경하는 것이 아니라 새로운 메모리 공간을 확보하고 재할당한 원시 값을 저장한 후, 변수는 새롭게 재할당한 원시 값을 가리킨다. 이러한 특성을 불변성이라 한다.
주의할 점은 원시 값 자체를 변경할 수 없다는 것이지 변수 값을 변경할 수 없음을 의미하지 않는다. 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없다.
값에 의한 전달
원시 값을 갖는 변수를 할당하면 할당 받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달이라 한다. 값은 동일하지만 다른 메모리 공간에 저장된 별개의 값이다.
var score = 80;
var copy = scroe;
console.log(score, copy); // 80, 80
console.log(score === copy); // true
주소(변수 영역) | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
데이터 | score / @5000 | copy / @5000 | - | - |
주소 (데이터 영역) | 5000 | 5001 | 5002 | 5003 |
---|---|---|---|---|
데이터 | 80 | - | - | - |
“값에 의한 전달”이라는 용어는 자바스크립트를 위한 용어가 아니므로 사실 오해가 있을 수도 있다. 엄격하게 표현하면 변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달되기 때문이다. 이는 변수와 같은 식별자는 값이 아니라 메모리 주소를 기억하고 있기 때문이다.
객체
객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다. 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체다.
객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 객체를 원시 값과 같이 다루지 않는 이유는 객체의 크기가 고정적이지 않고 프로퍼티 값이 객체일 수도 있어서 복사해서 생성하기에 비용이 많이 든다. 메모리 사용의 효율성과 성능을 위해 객체를 변경 가능한 값으로 설계한 것이다. 이러한 구조에 따른 부작용이 있다.
참조에 의한 전달
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라 한다.
ver person = { name: 'Lee' }
var copy = person;
copy.name = 'Park';
console.log(person.name, copy.name); // 'Park', 'Park'
console.log(person.name === copy.name); // true
주소(변수 영역) | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
데이터 | person / @7103 | copy / @7103 | - | - |
주소 (데이터 영역) | 5000 | 5001 | 5002 | 5003 |
---|---|---|---|---|
데이터 | ‘Park’ | - | - |
주소(person) | 7103 | 7104 | 7105 | 7106 |
---|---|---|---|---|
데이터 | name / |
- | - | - |
person과 copy는 동일한 참조 값을 갖는다. 하나의 객체를 공유하고 있어 어느 한쪽에서 객체를 변경하면 서로 영향을 주고받는다.
불변 객체 만들기
의도하지 않은 객체의 변경 원인의 대부분이 다른 객체에서 참조한 객체를 변경하기 때문이다. 이 문제의 해결 방법으로 객체를 불변 객체로 만든다. 마치 원시 값처럼 변경 불가능한 값으로 동작하게 만드는 것이다.
var person = { name: 'Lee' }
var changeName = function (user, newName) {
return { name: newName };
};
var copy = changeName(person, 'Park');
console.log(person, copy); // { name: 'Lee' } { name: 'Park' }
console.log(person === copy); // false
객체의 프로퍼티에 접근하는 것이 아니라 아예 새로운 객체를 반환하는 것이다. 원시 값과 같이 새로운 주소를 참조한다.
주소(변수 영역) | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
데이터 | person / @7103 | copy / @8200 | - | - |
주소 (데이터 영역) | 5000 | 5001 | 5002 | 5003 |
---|---|---|---|---|
데이터 | ‘Lee’ | ‘Park’ | - | - |
주소(객체 영역) | 7103 | 7104 | … | 8200 | 8201 |
---|---|---|---|---|---|
데이터 | name / @5000 | - | … | name / @5001 | - |
💬 객체 타입에 대한 메모리 접근을 완전히 이해했다고 생각했는데 오류가 있었다. 객체 내의 프로퍼티가 여러 개일 때 프로퍼티들을 저장하기 위한 별도의 변수 영역을 마련하고, 그 영역의 처음 주소
@7103
만을 가리킨다라고 알고 있었다. 프로퍼티의 처음 주소가 아니라 해당 프로퍼티의 주소부터 포함된 모든 영역을 저장한다@7103 ~ ?
.
얕은 복사, 깊은 복사
객체 복사는 얕은 복사와 깊은 복사 두 가지 복사 방법이 있다. 객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말한다.
얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사해서 원시 값처럼 완전한 복사본을 만든다.
// 얕은 복사
var user = {
name: 'changseong',
urls: {
blog: 'https://velog.io/@code_newb/',
}
}
var user2 = { ...user };
user2.urls.blog = 'https://scseong.github.io/'
console.log(user.urls.blog === user2.urls.blog) // true
// 깊은 복사
var user = {
name: 'changseong',
urls: {
blog: 'https://velog.io/@code_newb/',
},
};
var user2 = { ...user };
user2.urls = { ...user.urls };
user2.urls.blog = 'https://scseong.github.io/';
console.log(user.urls.blog === user2.urls.blog); // false
객체의 프로퍼티 중 기본형 데이터는 그대로 복사하고 참조형 데이터는 다시 그 내부의 프로퍼티를 복사해야 한다. 그런데 객체의 깊이가 깊다면 위와 같은 방법으로는 수행하기 어려울 것이다. 재귀적으로 깊은 복사를 수행한다.
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;
}
댓글남기기