반응형
변수 넘겨주는 방법
Pass(Call) by reference : 참조에 의한 전달
- object type을 객체 또는 참조 타입이라 한다. 참조 타입이란 객체의 모든 연산이 실제값이 아닌 참조값으로 처리됨을 의미한다. 원시 타입은 값이 한번 정해지면 변경할 수 없지만(immutable), 객체는 프로퍼티를 변경, 추가, 삭제가 가능하므로 변경 가능(mutable)한 값이라 할 수 있다.
- 따라서 객체 타입은 동적으로 변화할 수 있으므로 어느 정도의 메모리 공간을 확보해야 하는지 예측할 수 없기 때문에 런타임에 메모리 공간을 확보하고 메모리의 힙 영역(Heap Segment)에 저장된다.
var foo = { val: 10 };
var bar = { val: 10 };
console.log(foo.val, bar.val); // 10 10
console.log(foo === bar); // false
var baz = bar;
console.log(baz.val, bar.val); // 10 10
console.log(baz === bar); // true
- 변수 foo와 변수 bar는 비록 내용은 같지만 별개의 객체를 생성하여 참조값을 할당하였다. 따라서 변수 foo와 변수 bar의 참조값 즉 어드레스는 동일하지 않다.
- 변수 baz에는 변수 bar의 값을 할당하였다. 결국 변수 baz와 변수 bar는 동일한 객체를 가리키는 참조값을 저장하고 있다. 따라서 변수 baz와 변수 bar의 참조값은 동일하다.
var a = {}, b = {}, c = {}; // a, b, c는 각각 다른 빈 객체를 참조
console.log(a === b, a === c, b === c); // false false false
a = b = c = {}; // a, b, c는 모두 같은 빈 객체를 참조
console.log(a === b, a === c, b === c); // true true true
Pass (Call) by value : 값에 의한 전달
- 이에 반해 원시 타입은 값(value)으로 전달된다. 즉 값이 복사되어 전달되는데 이를 pass-by-value(값에 의한 전달)라 한다.
- 원시 타입은 값이 한번 정해지면 변경할 수 없다.(immutable) 또한 이들 값은 런타임(변수 할당 시점)에 메모리의 스택 영역(Stack Segment)에 고정된 메모리 영역을 점유하고 저장된다.
// Pass-by-value
var a = 1;
var b = a;
console.log(a, b); // 1 1
console.log(a === b); // true
a = 10;
console.log(a, b); // 1 10
console.log(a === b); // false
- 변수 a는 원시 타입인 숫자 타입 1을 저장하고 있다. 원시 타입의 경우 값이 복사되어 변수에 저장된다. 즉, 참조 타입으로 저장되는 것이 아니라 값 자체가 저장되게 된다. 변수 b에 변수 a를 할당할 경우, 변수 a의 값 1은 복사되어 변수 b에 저장된다.
객체와 변경불가성
- Immutability(변경불가성)는 객체가 생성된 이후 그 상태를 변경할 수 없는 디자인 패턴을 의미한다. Immutability은 함수형 프로그래밍의 핵심 원리이다.
- 객체는 참조(reference) 형태로 전달하고 전달 받는다. 객체가 참조를 통해 공유되어 있다면 그 상태가 언제든지 변경될 수 있기 때문에 문제가 될 가능성도 커지게 된다. 이것이 의도한 동작이 아니라면 참조를 가지고 있는 다른 장소에 변경 사실을 통지하고 대처하는 추가 대응이 필요하다.
immutable value과 mutable value
- Javascript에서 Boolean, null, undefined, Number, String, Symbol 등의 원시 타입(primitive data type)은 변경 불가능한 값(immutable value)이다.
- Javascript에서 원시 타입 이외의 모든 값은 객체(Object) 타입이며 객체 타입은 변경 가능한 값(mutable value)이다. 즉, 객체는 새로운 값을 다시 만들 필요없이 직접 변경이 가능하다는 것이다.
- 예를 들어 C 언어와는 다르게 Javascript의 문자열은 변경 불가능한 값(immutable value)으로 재할당은 가능하지만 메모리 영역에서의 변경이 불가능하다.
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'
var arr = [];
console.log(arr.length); // 0
var v2 = arr.push(2); // arr.push()는 메소드 실행 후 arr의 length를 반환
console.log(arr.length); // 1
- Stirng 객체의 slice() 메소드는 statement 변수에 저장된 문자열을 변경하는 것이 아니라 사실은 새로운 문자열을 생성하여 반환하고 있다. 그 이유는 문자열은 변경할 수 없는 immutable value이기 때문이다.
- 반면 배열 객체의 push() 메소드는 직접 대상 배열을 변경한다. 그 이유는 배열은 객체이고 객체는 변경 가능한 mutable 값이기 때문이다.
불변 데이터 패턴(immutable data pattern)
- 의도하지 않은 객체의 변경이 발생하는 원인의 대다수는 “레퍼런스를 참조한 다른 객체에서 객체를 변경”하기 때문이다. 이 문제의 해결 방법은 객체를 불변객체로 만들어 프로퍼티의 변경을 방지하며 객체의 변경이 필요한 경우에는 참조가 아닌 객체의 방어적 복사(defensive copy)를 통해 새로운 객체를 생성한 후 변경한다.
객체의 방어적 복사(defensive copy)
- Object.assign은 타깃 객체로 소스 객체의 프로퍼티를 복사한다. 이때 소스 객체의 프로퍼티와 동일한 프로퍼티를 가진 타깃 객체의 프로퍼티들은 소스 객체의 프로퍼티로 덮어쓰기된다. 리턴값으로 타킷 객체를 반환한다.
- Object.assign을 사용하여 기존 객체를 변경하지 않고 객체를 복사하여 사용할 수 있다. Object.assign은 완전한 deep copy를 지원하지 않는다. 객체 내부의 객체(Nested Object)는 Shallow copy된다.
Object.assign(타겟, ...소스들)
// 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 }
불변객체화를 통한 객체 변경 방지
- Object.freeze()를 사용하여 불변(immutable) 객체로 만들수 있다. 하지만 객체 내부의 객체(Nested Object)는 변경가능하다.
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'; // 무시된다!
user.address.city = 'Busan'; // 변경된다!
console.log(user1); // { name: 'Lee', address: { city: 'Seoul' } }
console.log(Object.isFrozen(user1)); // true
- 내부 객체까지 변경 불가능하게 만들려면 Deep freeze를 하여야 한다.
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' } }
Immutable.js
- Object.assign과 Object.freeze을 사용하여 불변 객체를 만드는 방법은 번거러울 뿐더러 성능상 이슈가 있어서 큰 객체에는 사용하지 않는 것이 좋다. 그래서 또 다른 대안으로 Facebook이 제공하는 Immutable.js를 사용하는 방법이 있다.
- Immutable.js는 List, Stack, Map, OrderedMap, Set, OrderedSet, Record와 같은 영구 불변 (Permit Immutable) 데이터 구조를 제공한다.
- npm을 사용하여 Immutable.js를 설치한 후 Immutable.js의 Map 모듈을 임포트하여 사용한다.
$ npm install immutable
- map1.set(‘b’, 50)의 실행에도 불구하고 map1은 불변하였다. map1.set()은 결과를 반영한 새로운 객체를 반환한다.
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
Reference
반응형
'Language > JavaScript' 카테고리의 다른 글
[Javascript] 자바스크립트 객체 분류 - 네이티브, 호스트 객체 (0) | 2023.08.02 |
---|---|
[Javascript] 자바스크립트 객체 지향 프로그래밍 - 추상, 캡슐, 상속, 다형 (0) | 2023.07.30 |
[Javascript] 자바스크립트 웹 Request(요청)과 Response(응답) (0) | 2023.07.23 |
[Javascript] 자바스크립트 예외 처리 - throw, try, strict (0) | 2023.07.19 |
[Javascript] 자바스크립트 상속 - 프로토 타입 생성자, 체인 (0) | 2023.07.19 |