기본(원시)형 데이터와 참조형 데이터 & 얕은 복사와 깊은 복사 & 데이터의 불변성 (feat. 할당과 재할당)
기본(원시)형 자료는 메모리에 값을 직접 저장하고, 참조형 자료는 값이 저장된 주소 값을 할당(참조)하는 것이다.
기본(원시)형 데이터와 참조형 데이터 & 얕은 복사와 깊은 복사
- 원시 데이터와 참조형 데이터의 정의와 종류는 아래 포스팅 참고
[20221118] JavaScript 특성과 자료구조
JavaScript의 특성 자바스크립트는 느슨한 타입(loose typed)의 동적 언어이다. JS에서 변수는 어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당 (및 재할당) 가능하다. 예를 들면, 상수 변수
dev-jn.tistory.com
- 위의 statement가 사실 잘 이해가 가지 않아, ppt에 직접 그려봤다.
- 불변값과 가변값의 개념을 간략하게 그림에 담아 보았다. 또, 얕은 복사와 깊은 복사도 간략하게 담아보았다. 모호했던 개념들이 조금은 정리가 되어 머리 속에 들어오는 느낌이다.
상수 값의 재할당
- const 값의 경우, 원시 타입의 경우, 상수 값을 변경할 수 없다. 하지만, 오브젝트를 const로 선언하면, 값을 변경할 수 있다. 그게 왜 그런지 너무나 헷갈렸어는데, 이게 다 얕은 복사 때문인거다. 즉, 오브젝트를 const로 선언을 하더라도, 변수가 가리키고 있는 메모리 공간에는 오브젝트 값이 주소하고 있는 주소 값이 할당되기 때문인거다... 오브젝트 리터럴을 사용해, 변수명이 아닌 오브젝트 키 값으로 직접 값에 접근하면 당연히 값이 재할당 되는 것이다.
const num = 1;
num = 2;
console.log(num); //TypeError: Assignment to constant variable.
const obj = { name: "JavaScript" };
obj.name = "JAVA";
console.log(obj); // { name: 'JAVA' }
얕은 복사를 만들어 내는 대표적인 메서드
- Array.prototype.slice()
- Array.prototype.filter()
- Object.assign() -> defensive copy
- Spread operator (...)
값의 불변성과 가변성 (immutable vs mutable value)
- 아래 예제를 통해 원시 값(불변)과 객체 값(가변)이 어떻게 다른지 알아보았다.
// 예제 1 : 문자열
var statement = 'I am an immutable value'; // string은 immutable value
var otherStr = statement.slice(8, 17);
console.log(otherStr); // 'immutable'
console.log(statement); // 'I am an immutable value'
// 예제 2 : 배열 -> 객체의 메소드가 직접 대상 배열을 변경
var arr = [];
console.log(arr.length); // 0
arr.push(2); // arr.push()는 메소드 실행 후 arr의 length를 반환
console.log(arr.length); // 1
// 예제 3 : 프로퍼티 함정 -> 가변 값 같아 보이는 객체이지만, 사실 불변인 원시값!
// 왜냐하면, myName에 객체인 user를 할당한게 아니라, 원시값인 string 타입의 "Lee" 값을 할당했기 때문!
var user = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
var myName = user.name; // 변수 myName은 string 타입이다.
user.name = 'Kim';
console.log(myName); // Lee
myName = user.name; // 재할당
console.log(myName); // Kim
// 예제 4: 프로퍼티
var user1 = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
var user2 = user1; // 변수 user2는 객체 타입이다.
user2.name = 'Kim';
console.log(user1.name); // Kim
console.log(user2.name); // Kim
불변 객체를 만드는 방법
- 어제 올린 포스팅에서 분명 "JS에서 타입은 원시값(premitives) 과 객체로 나눠지며, 객체를 제외한 모든 타입은 불변 값을 정의한다." 라고 정리했는데, 불변 객체라니???
- 얕은 복사(같은 레퍼런서를 참조한 다른 객체의 변경) 때문에, 의도치 않게 객체 값이 변경될 수 있고, 이를 방지하기 위해 비용은 조금 더 들지만 객체를 참조가 아닌 방어적 복사(defensive copy)를 통해 새로운 객체를 생성한 후 값을 변경함으로 해결할 수 있다.
- 객체의 방어적 복사(defensive copy) - Object.assign
- 불변객체화를 통한 객체 변경 방지 - Object.freeze
Object.assign(target, ...sources)
- target 객체(프로퍼티를 새로 담을 객체)로 sources 객체(복사 대상 객체)를 덮어 씌우고 target 객체를 반환하는 방식으로 참조값을 복사하는 대신, 프로퍼티를 복사한다. 다만, Object.assign(target, ...sources)의 리터럴에서만 이렇게 방어적 복사가 진행되며, 완전한 deep copy는 지원하지 않는다.
- 즉, 객체 내부의 객체(Nested Object)는 얕게 복사(shallow copy)된다
// Copy
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
console.log(obj == copy); // false
// Merge
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };
const merge1 = Object.assign(o1, o2, o3);
console.log(merge1); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }, 타겟 객체가 변경된다!
// Merge
const o4 = { a: 1 };
const o5 = { b: 2 };
const o6 = { c: 3 };
const merge2 = Object.assign({}, o4, o5, o6);
console.log(merge2); // { a: 1, b: 2, c: 3 }
console.log(o4); // { a: 1 }
//Nested copy example
const user1 = {
name: 'Lee',
address: { // address 값에는 {city: 'Seoul'}의 레퍼런스가 들어 있으며, user2에 shallow copy 된다
city: 'Seoul'
}
};
// 새로운 빈 객체에 user1을 copy한다.
const user2 = Object.assign({}, user1);
// user1과 user2는 참조값이 다르다.
console.log(user1 === user2); // false
user2.name = 'Kim';
console.log(user1.name); // Lee
console.log(user2.name); // Kim
// 객체 내부의 객체(Nested Object)는 Shallow copy된다.
console.log(user1.address === user2.address); // true
user1.address.city = 'Busan';
console.log(user1.address.city); // Busan
console.log(user2.address.city); // Busan
- Object.freeze()를 사용하여 불변(immutable) 객체로 만들수 있다.
- Object.assign과 동일하게, Nested Object는 변경이 가능하고, 내부 객체까지 freeze 시키려면, Deep Freeze를 하면 된다.
// Object.assign(target, ...sources) vs Object.freez()
const user1 = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
// Object.assign은 완전한 deep copy를 지원하지 않는다.
const user2 = Object.assign({}, user1, {name: 'Kim'});
console.log(user1.name); // Lee
console.log(user2.name); // Kim
Object.freeze(user1);
user1.name = 'Kim'; // 무시된다!
console.log(user1); // { name: 'Lee', address: { city: 'Seoul' } }
console.log(Object.isFrozen(user1)); // true
//Object.freeze()를 써도 Nested Object는 여전히 변경 가능(해당 프로퍼티는 Deep copy 되지 않음)
const user = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
Object.freeze(user);
user.address.city = 'Busan'; // 변경된다!
console.log(user); // { name: 'Lee', address: { city: 'Busan' } }
//Deep freeze를 사용하여 내부 객체까지 Immutable하게 만들기
function deepFreeze(obj) {
const props = Object.getOwnPropertyNames(obj);
props.forEach((name) => {
const prop = obj[name];
if(typeof prop === 'object' && prop !== null) {
deepFreeze(prop);
}
});
return Object.freeze(obj);
}
const user = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
deepFreeze(user);
user.name = 'Kim'; // 무시된다
user.address.city = 'Busan'; // 무시된다
console.log(user); // { name: 'Lee', address: { city: 'Seoul' } }
- Meta(구 facebook)에서 제공하는 Imuttable.js는 Obejct.assign과 Object.freeze (번거롭고, 성능상의 이슈가 있어 큰 객체에는 사용 지양)의 대안으로 사용 가능하다.
- Immutable.js는 List, Stack, Map, OrderedMap, Set, OrderedSet, Record와 같은 영구 불변 (Permit Immutable) 데이터 구조를 제공한다.
// Immutable.js를 사용하기 위해서는 npm을 사용해 모듈을 설치해야 한다.
$ npm install immutable
// 모듈을 임포트 해서 사용한다
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50
Immutability | PoiemaWeb
함수형 프로그래밍의 핵심 원리이다. 객체는 참조(reference) 형태로 전달하고 전달 받는다. 객체가 참조를 통해 공유되어 있다면 그 상태가 언제든지 변경될 수 있기 때문에 문제가 될 가능성도
poiemaweb.com
'항해99_10기 > 105일의 TIL & WIL' 카테고리의 다른 글
[1주차 WIL] 2022.11.14 ~ 2022.11.20 회고 (0) | 2022.11.20 |
---|---|
[20221120] JavaScript 호이스팅과 TDZ (변수은닉화 내용 추가 예정) (0) | 2022.11.20 |
[20221118] JavaScript 특성과 자료구조 (0) | 2022.11.19 |
[20221117] 1주차 풀스택 미니 프로젝트 회고 (0) | 2022.11.18 |
[20221116] .env로 민감정보 감추기 (language : python) (0) | 2022.11.17 |