치춘짱베리굿나이스
자바스크립트에서의 Symbol 본문
Symbol
뭐 하는 녀석인지?
태초에 자바스크립트는 원시 자료형 5개와 객체 자료형 1개 총 6개의 자료형으로 이루어져 있었다
Number
, String
, Boolean
, null
, undefined
그리고 객체 Object
가 그것이었는데, ES6 (2015년) 에 원시자료형팀 환상의 식스맨으로 Symbol
이 합류했다
심볼은 객체의 고유 식별자로 활용할 수 있는 원시자료형으로, 쉽게 말해 객체 내부 프로퍼티의 키를 설정할 때 사용할 수 있다
심볼을 사용하면 프로퍼티 키가 겹치지 않고 고유한 값으로 설정되므로, 키가 중복으로 설정됨으로써 발생하는 충돌을 막을 수 있다
프론트엔드에서 클래스명을 겹치지 않게 설정하기 위해 CSS module을 사용하거나, CSS-in-JS 라이브러리들 (Styled Components, Emotion) 등을 사용하는 이유와 같다고 보면 된다
Symbol 선언 및 초기화하기
선언 및 초기화
const newSymbol = Symbol();
간단하다
생성자 쓰듯이 Symbol();
함수를 호출하면 새로운 심볼이 생성된다
const newSymbol = new Symbol();
// TypeError 발생
단 Symbol()
함수는 생성자가 아닌 단순 함수이므로, new
를 붙여 호출하면 타입 에러가 발생한다
const foo = Symbol('symbol');
함수의 인자로 문자열이나 숫자를 넣을 수 있으며, 이는 내부적으로 디버깅 시에 심볼 식별용으로 사용된다
내부적으론 description
이라고 부른다
말 그대로 이 심볼을 설명해줄 수 있는 요소라고 보면 된다
실제로 저 값이 심볼 생성에 영향을 미치진 않는다
전역으로 선언하기 (전역 심볼 레지스트리에 등록하기)
const foo = Symbol.for("chichoon");
Symbol.for
메서드를 사용하면 전역 레지스트리에 심볼을 등록하거나, 찾아올 수 있다
인자로 키를 하나 받으며, 이 키에 대한 심볼이 전역 레지스트리에 등록되어 있을 경우 이를 반환하고, 없으면 새로 생성한다
function foo() {
const sym = Symbol.for("foo");
return sym;
}
function test() {
const symbolA = foo();
const symbolB = Symbol.for("foo");
console.log(symbolA === symbolB);
}
test();
테스트를 위해 함수 스코프를 아예 분리해 보았다
Symbol.for(”foo”)
는 foo
함수 내에서 호출되었으므로, 다른 함수 test
에서 호출한 Symbol.for(”foo”)
는 다른 값을 가져야 할 것 같다
하지만 foo
에서 Symbol.for(”foo”)
로 생성한 symbolA
와, test
에서 Symbol.for(”foo”)
로 생성한 symbolB
를 비교하면 같은 값이라고 나온다
이는 foo
함수 내에서 Symbol.for(”foo”)
를 호출함으로써 심볼이 전역 심볼 레지스트리에 등록되었기 때문으로, 인자로 넣어준 “foo”
는 전역 심볼 레지스트리에서 이 심볼을 찾기 위한 식별자 (key) 로 사용된다
test
함수 내에서 호출된 Symbol.for(”foo”)
는, foo
함수에 의해 심볼 레지스트리에 이미 “foo”
라는 식별자를 가진 심볼이 등록되었으므로 이를 그대로 반환하고, 따라서 두 값 (symbolA
, symbolB
) 은 완벽히 같은 값을 가리키게 된다
console.log(Symbol.keyFor(symbolA));
이렇게 전역 레지스트리에 등록한 심볼은 keyFor
메서드를 이용해서 식별자를 다시 가져올 수 있다
특이점
console.log(typeof symbol1);
typeof
은 “symbol”
을 반환한다
고유한 하나의 자료형이라 그렇다
const symbol1 = Symbol("symbol");
const symbol2 = Symbol("symbol");
console.log(symbol1 === symbol2);
위에 적었듯 description
은 단순히 디버깅 시 설명용으로만 사용되므로, 같은 인자를 넘겨줬다고 해서 같은 값이 되진 않는다
symbol1
과 symbol2
는 같은 description (”symbol”
) 을 인자로 넘겨받은 심볼이지만, 비교연산을 사용해보면 둘이 다른 값이라고 출력된다
전역 레지스트리에서 같은 값을 참조하지 않는 한, 모든 심볼은 서로 다른 고유의 값을 갖는다
선언한 Symbol 사용해보기
용례: 객체의 key로 사용하기
const obj = {};
const symbolA = Symbol("symbolA");
const symbolB = Symbol("symbolB");
obj[symbolA] = "hello";
obj[symbolB] = "world";
console.log(obj);
실제로 객체의 key로 응용해 보았다
출력해보면 객체의 키에는 [Symbol(<description으로 넘겨준 인자>)]
가 들어가고 있다
const obj = {};
const symbolA = Symbol();
const symbolB = Symbol();
obj[symbolA] = "hello";
obj[symbolB] = "world";
console.log(obj);
description
에 아무런 인자를 넣지 않아도 고유한 값으로 인식되어 들어간다
출력할 때만 똑같이 보일 뿐..
이처럼 description
에 무슨 값이 들어가든 Symbol 변수는 객체의 키로 활용될 때 절대 고유한 유일의 값으로 사용된다는 것을 알 수 있다
거대한 객체를 다룰 때 객체의 key를 나도 모르게 겹치는 것으로 설정해버려서 기존 값을 덮어씌워버리는 대참사를 막기 좋다
다만 심볼은 매번 생성할 때마다 달라지므로, 키를 나중에 또 쓰고 싶다면 잘 보관하거나 전역 레지스트리에 보관해야 하것다
내장-미리된 심볼
위처럼 우리가 직접 선언해서 사용하는 심볼 말고도, 자바스크립트 엔진 자체에 미리 내장되어 있는 심볼들이 존재한다
Built-in Symbol, 또는 Well-Known Symbol이라고 하며, 순회나 정규식 사용 등의 기능을 제공한다
Symbol.iterator
제일 유명한 내장 심볼이 Symbol.iterator
일 것이다
이 Symbol.iterator
은 특정 객체를 순회가능하게 (Iterable) 만들어준다
순회가능하다는 것은, for … of
문에서 객체를 사용할 수 있다는 것이다 (댑악)
const obj = {
a: "hello",
b: "world",
c: "this",
d: "is",
e: "chichoon",
};
const str = "";
for (let value of obj) str += value + " ";
console.log(str);
원래 이렇게 TypeError가 발생하면서 순회를 할 수 없었다
이 객체를 순회가능한 객체로 만들어 for문으로도 값에 접근할 수 있도록 해보자
const obj = {
a: "hello",
b: "world",
c: "this",
d: "is",
e: "chichoon",
[Symbol.iterator]: function () {
let values = Object.values(this);
let index = 0;
return {
next: () => ({
value: values[index++],
done: index > values.length,
}),
};
},
};
객체의 내부에 [Symbol.iterator]
을 키로 갖는 프로퍼티를 추가한다
이 프로퍼티는 함수 형태로, value
와 done
을 반환하고 인자 없는 함수이다
앞서 적었듯 Symbol
은 객체의 고유한 키로 사용되는 자료형이므로, Symbol.iterator
도 고유한 키로 사용될 수 있다
next: () => ({
value: values[index++],
done: index > values.length,
}),
Symbol.iterator
프로퍼티는 next
라는 내부 함수를 반환하는 함수이다
next
함수는 아무런 인자를 받지 않고 단지 value
, done
이라는 프로퍼티를 가진 객체를 반환하는데,
value
: 현재 객체를for
문 등으로 순회할 때 반환할 값done
: 순회 종료 조건
해당 객체는 순회할 때 필요한 요소들을 담고 있다
코드를 천천히 뜯어보면,
values
는 본 객체 (obj
) 의 값들의 배열이다 (Object.values
메서드로 가져온 값 배열)index
는 0부터 시작하며,Symbol.iterator
메서드의 스코프 내에서 1씩 증가한다Symbol.iterator
메서드는next
라는 함수를 반환하고 종료된다. 이때next
함수는 클로저로,Symbol.iterator
메서드의 실행이 끝나더라도 자신이 속한 렉시컬 스코프 (Symbol.iterator
메서드의 스코프) 내의 변수들인values
,index
를 참조할 수 있다- 따라서
next
함수를 호출할 때마다Symbol.iterator
스코프에 있는index
변수가 1씩 증가한다 index
변수가 후위연산자에 따라 1씩 증가하면,value
또한 인덱스가 증가하면서 다음 값을 가리키게 된다index
가 계속 증가하면서 다음value
를 가리키다가, 특정 시점이 되면done
조건을 만족해 순회가 종료된다
for문을 통해 배열이나 문자열을 순회하는 것은 결국 매 루프마다 next
함수를 호출하는 것이라고 볼 수 있겠다
클로저니 렉시컬 스코프니 하는 용어들이 나왔는데, 함수형 프로그래밍 관련 용어니 이것도 다 정리해두는 게 좋겠다
let str = "";
for (let value of obj) str += value + " ";
console.log(str);
Symbol.iterator
을 적용한 객체를 다시 반복문으로 순회해 보면 제대로 동작하여 str
문자열이 잘 만들어지는 것을 볼 수 있다 (신기방기)
Symbol.iterator
은 이처럼 반복문으로 순회할 수 없는 객체를 순회가능하도록 만들어 준다
위의 예시는 단순히 객체의 값을 순서대로 순회하도록 했지만, 조건문이나 옵션을 추가해서 순회 방식을 커스텀할 수도 있다
let iterator = "hello world"[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
built-in iterator의 대표적인 예로 문자열 자료형 (String
) 이 있는데, 실제로 Symbol.iterator
메서드를 불러와 next
함수를 호출할 때마다 내부적으로 index
(또는 그러한 역할을 하는 변수) 의 값이 증가하기 때문에 value
가 변하는 것을 볼 수 있다
배열이나 Map
, Set
등의 자료형들도 내부적으론 Symbol.iterator
을 갖고 있다
기타
이런 키워드나 자료형이 그렇듯이 어떻게 활용할지가 주 관건인 것 같다
Symbol.iterator
은 객체를 배열처럼 순회할 수 있도록 다룰 때 종종 사용돼서 편리하더라
여러 코드를 리뷰하면 이런 새로운 개념들도 알게 되고 참 좋은 것 같다 (뜬금)
참고자료
Symbol.for() - JavaScript | MDN
Symbol.keyFor() - JavaScript | MDN
Everything you need to know about JavaScript symbols
Iteration protocols - JavaScript | MDN
'Javascript + Typescript > 이론과 문법' 카테고리의 다른 글
콜 스택 (호출 스택) (0) | 2022.08.27 |
---|---|
자바스크립트 일반함수 vs 화살표함수 (1) | 2022.08.27 |
자바스크립트에서의 싱글톤 패턴과 static (0) | 2022.08.08 |
Set, Map (0) | 2022.07.30 |
자바스크립트에서의 함수형 프로그래밍 (0) | 2022.07.30 |