[JavaScript] Symbol 값과 이터러블 - 1

Hyuk·2023년 2월 12일
0
post-thumbnail

자바스크립트를 배우면서 for문을 배우고 자연스레 for ...infor ...of 도 배웠다.
당시 for ...infor ...of 의 차이는 단순히 객체 순환과 배열 순환 할 때의 차이라고 알고 있었다.

for ...of 의 동작 원리가 ES6에서 새롭게 추가된 데이터 타입인 Symbol 과 밀접한 관계가 있다는 것을 알게 되면서 Symbol 에 대해 더 자세히 알아 보고 싶었다.

Symbol

Symbol 은 ES6에서 새롭게 추가된 원시 타입의 값이다.
원시 타입의 값들로는 Number, String, undefined 등이 있다.

각각 이름만 보고서도 어떤 유형인지 가늠이 가지만, Symbol 은 잘 가늠이 가지 않는다.

Symbol 의 가장 큰 특징은 다른 값과 절대 중복되지 않는 유일무이한 값이다.
따라서 이러한 특성을 통해 이름의 충돌 위험이 없는 유일한 프로퍼티 키의 용도로 사용된다.

Symbol.for 메서드

다음과 같이 Symbol 값을 생성해보자.

const Symbol1 = Symbol.for('name')

Symbol 값은 다른 값과 절대 중복되지 않는 유일무이한 값이다.
따라서 Symbol 값을 생성하기에 앞서 기존에 같은 이름으로 생성되어 있는지 확인하고 생성할 필요가 있다.

이를 위한 메서드가 Symbol.for 메서드이다.

위의 코드의 예시를 보자.
name 이라는 키로 저장된 Symbol 값이 있는지 탐색하고 없다면 생성, 있다면 해당 값을 반환한다.

const Symbol1 = Symbol.for('name')
const Symbol2 = Symbol.for('name')

console.log(Symbol1 === Symbol2)   // true

Symbol 프로퍼티 키

기존에 필자는 객체의 프로퍼티 키를 동적으로 생성하고 싶었을 때 다음과 같이 코드를 작성한 적이 있다.

let tasks = {};
const ID = Date.now().toString();

const newTaskObject = {
  [ID]: {id: ID, task: '' ,complete: false}
};

tasks = {...tasks, ...newTaskObject};

Date 함수를 통해 매번 새로운 id 을 생성해 키로 사용한 적이 있다.

Symbol 값은 절대 중복되지 않는 값을 보장해 주니 다음과 같이 Symbol 값을 통해 더욱 견고한 객체의 프로퍼티 키을 생성할 수 있다.

let tasks = {};
const ID = Date.now().toString();

const newTaskObject = {
	[Symbol.for(ID)]: {id: ID, task: '' ,complete: false}
};
tasks = {...tasks, ...newTaskObject};

Symbol 프로퍼티 은닉

객체는 일반적으로 for ...in 문을 통해 순환이 가능하다.

하지만 Symbol 값을 통해 생성한 객체 프로퍼티 키는 for ...in 문으로 순환이 불가능하다.
정확히 말하면 순환이 불가능한 것이 아니라, Symbol 값을 찾지 못한다.

다음 예제는 위에서 생성한 예제 코드에서 출력해보았다.

for (const key in tasks) {
    console.log(key)   // undefined
}

따라서 Symbol 값으로 생성한 객체 프로퍼티 키는 외부로부터 은닉이 가능하다.


이터러블

이터러블은 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값이다.
이터레이터는 { value, done } 을 리턴하는 next() 를 가진 값이다.
이터러블/이터레이터 프로토콜은 이터러블을 for ...of, 전개 연산자 등과 함께 동작하도록 한 규약을 뜻한다.

사실 정의는 위와 같지만,
이터러블 프로토콜을 따르는 for ...of 문의 동작 원리를 바탕으로 이해하면 더욱 쉽다.

for ... of 문

개요

ES5 까지 배열을 순회 할 때 다음과 같이 for 문을 통해 순회가 가능했다.

const arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
	console.log(arr[i]);   // 1 2 3 
}

그리고 ES6 에서 도입된 for ...of 문을 통해서도 순회가 가능하다.

const arr = [1, 2, 3];
for (const a of arr) {
	console.log(a);   // 1 2 3
}

동작 원리

먼저 for ...of 문은 배열 뿐만이 아니라 setmap 자료구조에서도 순회가 가능하다.

const set = new Set([1, 2, 3]);
for (const a of set) {
	console.log(a);   // 1 2 3
}

하지만 setmap 자료구조에서는 배열과는 다르게 기존 for 문과 같이 키로 접근이 불가능하다.

console.log(set[0])  // undefined
console.log(set[1])  // undefined

그렇다는 말은 for ...of 문의 동작은 각 자료구조의 키를 통한 것이 아니라는 것을 알 수 있다.

Symbol.iterator

위의 Symbol 에서 보았듯이 Symbol 은 어떤 객체의 키로 사용될 수 있다.

그리고 다음과 같이 배열에 Symbol.iterator 라는 키로 접근해 보면 어떤 함수가 존재한다는 걸 알 수 있고,
Symbol.iterator 라는 키를 null 값으로 할당하면 순회가 되지 않는다는 것도 알 수 있다.

console.log(arr[Symbol.iterator])   // ƒ values() { [native code] }  

arr[Symbol.iterator] = null;

for (const a of arr) {
	console.log(a)
}

이터레이터

위에서 언급했듯이 이터러블과 이터레이터의 정의는 다음과 같다.

이터러블은 Symbol.iterator 를 실행했을 때 이터레이터를 리턴하게 되고,
그 이터레이터는 next() 메서드를 통해서 { value, done } 객체를 리턴한다.

코드로 이해해보면 다음과 같다.

const arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]();

iterator.next()   // { value: 1, done: false }
iterator.next()   // { value: 2, done: false }
iterator.next()   // { value: 3, done: false }
iterator.next()   // { value: undefined, done: true }
for (const a of arr) {
	console.log(a);   // 1 2 3
}

즉, for ...of 문의 동작 원리는 객체의 프로퍼티 키를 통한 것이 아닌,
Symbol.iterator 를 실행해 리턴한 이터레이터의 done 값이 true가 되기 전까지 value 값을 변수 a 에 담아 출력하게 된다.

따라서 배열과 set, map 자료구조는 이터러블이라고 할 수 있고,
이터러블/이터레이터 프로토콜을 따른다고 할 수 있다.

profile
프론트엔드 개발자

0개의 댓글