이번 포스팅에서는 ES6 에 추가된 전개 구문(Spread syntax, ...) 활용 트릭을 몇 가지 소개하려고 한다.
구글링을 해보면 꽤 많은 트릭과 활용 방법들이 있는데, 그 중 간단하면서 자주 사용할 수 있는 내용으로 구성해보았으니 한 번 알아보도록 하자.
Rest parameter
나머지 파라미터
Rest parameter는 전개 구문에 해당하는 내용은 아니지만
눈에 보이는 코드 상으로 유사하니
한 번 알아보려고 한다.
Rest parameter는 이미 널리 사용하고 있을텐데, 함수의 매개변수 갯수가 정해져있지 않을 때 사용하게 된다.
const sum = (acc, ...nums) => {
for (let num of nums) {
acc += num;
}
return acc;
};
sum(0, 10, 20, 30); // 60
위와 같이 정해지지 않은 갯수의 매개변수를 받는 함수를 ...variable
형태로 구현할 수 있다.
Rest 파라미터는 함수에 바인딩되는 arguments
와 다르게 유사 배열이 아닌 자바스크립트 표준 배열로 대체되며,
함수의 마지막 매개변수만 Rest parameter 가 될 수 있다는 제약조건이 있다.
// Error
const f = (a, ...b, c) => {
}
마지막 파라미터만 Rest 파라미터가 될 수 있기 때문에 위의 코드는 문법적으로 유효하지 않다.
Immutable data pattern
불변 데이터 패턴
자바스크립트의 경우 **원시 타입(Primitive type)**은 모두 **불변 값(immutable value)**이다.
원시 타입 외의 값들은 모두 변경 가능(mutable)하며 대표적으로 object 타입이 이에 해당한다.
아래 예제로 위 설명을 이해해보도록 하자.
var myStr = 'Hello';
myStr = 'World';
문자열(string) 타입은 자바스크립트의 원시 타입에 해당한다.
myStr
변수는 'Hello'
라는 문자열 리터럴 값을 참조하고 있었고, 그 아래에서 'World'
라는 문자열을 참조하고 있다.
'Hello'
라는 문자열 값이 'World'
로 바뀐것이 아니라 참조(reference)하던 대상이 변경된 것이다.
메모리상에서 'Hello'
라는 문자열 데이터와 'World'
라는 문자열 데이터가 독립적으로 생성되었고, myStr
변수가 이를 참조했을 뿐이다.
var obj = {
name: 'Tom',
};
var objClone = obj;
objClone.name = 'Jerry';
반대로 원시 타입이 아닌 다른 객체들은 값을 변경할 수 있다.
위 코드를 확인해보면 obj
객체에는 name
프로퍼티가 존재하고 Tom
이라는 문자열 값이 들어있다.
objClone.name = 'Jerry'
라는 코드가 실행되면 객체는 mutable value
이므로 objClone
의 name
값이 변경된다.
여기서 한 가지 문제점이 있다면 기존의 obj
의 name
값도 함께 변경되는 문제가 발생한다.
메모리상에 존재하는 하나의 객체를 obj
, objClone
이 함께 참조하고 있기 때문이다.
위의 코드가 의도한 동작이 아니라면 디버깅을 진행할 때 꽤 골치 아플 수 있다.
이 때문에 immutable 한 객체가 필요한 경우 Object.freeze
, Object.assign
등을 통해 모습은 같지만 서로 다른 객체를 만들어 참조 문제를 해결하는데, 전개 구문을 활용하여 Immutable 객체를 생성할 수 있다.
(React를 사용하는 경우 자주 직면할 것이다.)
const a = {
name: 'Tom',
age: 10,
};
const b = a;
b.name = 'Jerry'; // a, b 의 name 모두 변경된다.
const c = { ...a };
console.log(a === b); // true
console.log(a === c); // false
위 방식은 a
가 참조하고 있는 객체를 얕은 복사하여 c
에 할당한다.
만약 a
가 참조하고 있는 객체 내부에서 원시값이 아닌 다른 객체(Nested object)가 있다면, 해당 객체의 참조는 그대로 유지되기 때문에 얕은 복사라고 이야기한다. (a
가 참조하던 객체만 복사했을 뿐, 안에 들어있는 객체는 복사되지 않았기 때문)
객체 내에서 참조하고 있는 객체 까지 모두 복사를 하려면 깊은 복사를 진행해야 한다.
한 가지 예를 들어보자.
좋아하는 것에 대한 정보(객체)를 person
이 가지고 있었고,person
을 복사하여 otherPerson
에 새로운 객체를 할당했다.
복사된 객체인 otherPerson
의 favorite
은 기존 person
이 가지고 있던 faroviteThing
객체를 그대로 참조하는 형태가 된다.
이 상태에서 otherPerson.favorite 값을 수정하면 person 의 favorite 도 함께 변경된다.
메모리를 들여다보면, person
, otherPerson
, favoriteThing
3가지 객체만이 존재하는 모습일 것이다.
사람만 복제되어 두개의 객체가 되었을 뿐 좋아하는 것 하나를 서로 참조하고 있는 형태이다.
var favoriteThing = {
type: 'Animal',
name: 'Cat',
};
var person = {
name: 'Tom',
favorite: favoriteThing,
};
var otherPerson = { ...person };
// true
console.log(person.favorite === otherPerson.favorite);
이처럼 중첩된 상태의 객체를 모두 복사하려면 깊은 복사를 수행해야 하고, 깊은 복사는 여러 라이브러리에서 유틸로 지원하거나 얕은 복사를 재귀적으로 반복하여 구현할 수 있으니 참고하면 좋을 것 같다.
Optional spreading
선택적 전개
const available = true;
const obj = {
user: 'James',
age: 16,
...available && { key: 'kygX1LWpWr' },
};
console.log(obj); // { user: "James", age: 16, key: "kygX1LWpWr" }
available
변수는 유효한 상태를 나타내는 변수라고 하자.
obj
가 참조하는 객체에는 user
, age
프로퍼티가 고정적으로 존재하며, key
프로퍼티는 조건이 유효할 때에만 프로퍼티에 추가되어야 한다. (즉, 조건에 따라 포함될 수도, 안 될 수도 있는 "선택적" 인 프로퍼티이다)
위 예제 코드는 key
프로퍼티를 선택적으로 추가할 수 있도록 작성한 코드라고 볼 수 있다.
a && b
연산은 a
가 truety value일 때 b
의 값을 반환한다.a || b
는 반대로 a
가 falsy value일 때 b
의 값을 반환한다.
흔히 사용하는 AND, OR 논리연산자가 맞지만 실제 동작은 조건에 따라 앞, 뒤의 피연산자가 반환된다.
(이 부분에 대해서는 나중에 다시 이야기 해보도록 하겠다)
truety, falsy 값이라 함은 논리연산시 true
또는 false
로 평가되는 값을 의미하며,
대표적으로 아래의 값을 예로 들 수 있다.
- Truety: 1, '비어있지 않은 문자열', { k: 1 }, [1, 2, 3], {}, []
- Falsy: 0, '', NaN, undefined, null
(falsy 값을 제외한 모든 값은 truety 값으로 봐도 된다)
본론으로 넘어오자면 available
변수의 값이 truety 값인 경우 AND 연산자 뒤의 { key: '값' }
이 반환된다.
논리 연산 이후 전개 구문(...) 부분이 평가되기 때문에 ...{ key: '값' }
형태가 된다.
반대로 available
변수의 값이 falsy 값인 경우 false
가 반환되며 false
는 객체가 아니기 때문에 obj
에는 아무런 영향을 주지 않게 된다.
그냥 쉽게 key
프로퍼티에 빈 값을 할당해도 되지만, 아래와 같이 객체의 key 값에 의존하는 로직이 존재하는 경우가 있을 수 있다.
const obj = {
// 중략...
key: available ? { key: '값' } : undefined,
};
Object.keys(obj); // ['user', 'age', 'key'];
이처럼 조건에 따라 user, age 프로퍼티만 가지는 객체 혹은 user, age, key 모두 가지는 객체를 단 한 줄로 만들 수 있다.
지금까지 3가지 유형을 알아보았다.
알아두면 유용하게 사용하는 경우가 분명 생길테니 알아두면 좋을 것 같다.