ECMAScript로 표준화된 이래로 자바스크립트에는 6개의 타입, 즉, 문자열, 숫자, 불리언, undifined, null, 객체
심벌은 ES6에서 도입된 7번째 데이터타입으로 변경 불가능한 심벌값은 다른 값과 중복 되지않는 유일무이한 값.
10.3절 "프로퍼티"에서 살펴본 바와 같이 프로퍼티 키로 사용할 수 있는 값은 빈문자열을 포함하는 모든 문자열 or 심벌값.
다른 값과 절대 중복되지 않는 유일무이한 값.
// Symbol 함수를 호출하여 유일무이한 심벌 값을 생성한다.
const mySymbol = Symbol();
console.log(typeof mySymbol); // symbol
// 심벌 값은 외부로 노출되지 않아 확인할 수 없다.
console.log(mySymbol); // Symbol()
new 연산자와 함께 호출 하지 않음.
new 연산자와 함께 생성자 함수 또는 클래스를 호출하면 객체가 생성 되지만 심벌 값은 변경 불가능한 원시 값.
new Symbol(); // TypeError: Symbol is not a constructor
// 심벌 값에 대한 설명이 같더라도 유일무이한 심벌 값을 생성한다.
const mySymbol1 = Symbol('mySymbol');
const mySymbol2 = Symbol('mySymbol');
console.log(mySymbol1 === mySymbol2); // false
const mySymbol = Symbol('mySymbol');
// 심벌도 레퍼 객체를 생성한다
console.log(mySymbol.description); // mySymbol
console.log(mySymbol.toString()); // Symbol(mySymbol)
const mySymbol = Symbol();
// 심벌 값은 암묵적으로 문자열이나 숫자 타입으로 변환되지 않는다.
console.log(mySymbol + ''); // TypeError: Cannot convert a Symbol value to a string
console.log(+mySymbol); // TypeError: Cannot convert a Symbol value to a string
const mySymbol = Symbol();
// 불리언 타입으로는 암묵적으로 타입 변환된다.
console.log(!!mySymbol); // true
// if 문 등에서 존재 확인을 위해 사용할 수 있다.
if (mySymbol) console.log('mySymbol is not empty.');
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값을 생성
const s1 = Symbol.for('mySymbol');
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 있으면 해당 심벌 값을 반환
const s2 = Symbol.for('mySymbol');
console.log(s1 === s2); // true
Symbol.keyFor 메서드를 사용하면 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출 o.
// 전역 심벌 레지스트리에 mySymbol이라는 키로 저장된 심벌 값이 없으면 새로운 심벌 값을 생성
const s1 = Symbol.for('mySymbol');
// 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출
Symbol.keyFor(s1); // -> mySymbol
// Symbol 함수를 호출하여 생성한 심벌 값은 전역 심벌 레지스트리에 등록되어 관리되지 않는다.
const s2 = Symbol('foo');
// 전역 심벌 레지스트리에 저장된 심벌 값의 키를 추출
Symbol.keyFor(s2); // -> undefined
예를 들어 4방향, 즉 위,아래,왼쪽,오른쪽을 나타내는 상수를 정의 한다고 생각 해보자.
// 위, 아래, 왼쪽, 오른쪽을 나타내는 상수를 정의한다.
// 이때 값 1, 2, 3, 4에는 특별한 의미가 없고 상수 이름에 의미가 있다.
const Direction = {
UP: 1,
DOWN: 2,
LEFT: 3,
RIGHT: 4
};
// 변수에 상수를 할당
const myDirection = Direction.UP;
if (myDirection === Direction.UP) {
console.log('You are going UP.');
}
enum
enum은 명명된 숫자 상수의 집합으로 열거형 이라 부름. j.s는 enum을 지원하지 않지만 타입스크립트에선 지원.
근데 자바스크립트에서 enum을 흉내 내어 사용하려면 다음과 같이 객체의 변경을 방지하기 위해 객체를 동결하는 Object.freeze메서드와 심벌 값을 사용.// JavaScript enum // Direction 객체는 불변 객체이며 프로퍼티는 유일무이한 값이다. const Direction = Object.freeze({ UP: Symbol('up'), DOWN: Symbol('down'), LEFT: Symbol('left'), RIGHT: Symbol('right') }); const myDirection = Direction.UP; if (myDirection === Direction.UP) { console.log('You are going UP.'); }
33.4 심벌과 프로퍼티 키
객체의 프로퍼티 키는 빈 문자열을 포함되는 모든 문자열 또는 심벌 값으로 만들 수 있으며, 동적으로 생성 할 수 있음.
const obj = {
// 심벌 값으로 프로퍼티 키를 생성
[Symbol.for('mySymbol')]: 1
};
obj[Symbol.for('mySymbol')]; // -> 1
심벌 값은 유일무이한 값이므로 심벌 값으로 프로퍼티 키를 만들면 다른 프로퍼티 키와 절대 충돌하지 않는다.
심벌 값을 프로퍼티 키로 사용하여 생성한 프로피티는 for...in 문이나 Object.keys, Object.getOwnPropertyNames 메서드로 찾을 수 없음.
프로퍼티를 생성하면 외부에 노출할 필요가 없는 프로퍼티를 은닉할 수 있음.
const obj = {
// 심벌 값으로 프로퍼티 키를 생성
[Symbol('mySymbol')]: 1
};
for (const key in obj) {
console.log(key); // 아무것도 출력되지 않는다.
}
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []
const obj = {
// 심벌 값으로 프로퍼티 키를 생성
[Symbol('mySymbol')]: 1
};
// getOwnPropertySymbols 메서드는 인수로 전달한 객체의 심벌 프로퍼티 키를 배열로 반환한다.
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(mySymbol)]
// getOwnPropertySymbols 메서드로 심벌 값도 찾을 수 있다.
const symbolKey1 = Object.getOwnPropertySymbols(obj)[0];
console.log(obj[symbolKey1]); // 1
// 표준 빌트인 객체를 확장하는 것은 권장하지 않는다.
Array.prototype.sum = function () {
return this.reduce((acc, cur) => acc + cur, 0);
};
[1, 2].sum(); // -> 3
표준 빌트인 객체에 사용자 정의 메서드를 직접 추가하여 확장하는 것은 권장x 읽기 전용으로 사용 하는게 좋다.
이유
개발자가 직접 추가한 메서드와 미래에 표준사양으로 추가될 메서드의 이름이 중복 될 수 있기 때문.
// 심벌 값으로 프로퍼티 키를 동적 생성하면 다른 프로퍼티 키와 절대 충돌하지 않아 안전하다.
Array.prototype[Symbol.for('sum')] = function () {
return this.reduce((acc, cur) => acc + cur, 0);
};
[1, 2][Symbol.for('sum')](); // -> 3
자바스크립트가 기본제공하는 빌트인 심벌 값이 있음.
// 1 ~ 5 범위의 정수로 이루어진 이터러블
const iterable = {
// Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수
[Symbol.iterator]() {
let cur = 1;
const max = 5;
// Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터를 반환
return {
next() {
return { value: cur++, done: cur > max + 1 };
}
};
}
};
for (const num of iterable) {
console.log(num); // 1 2 3 4 5
}
이터러블 프로토콜
이터러블 프로토콜을 준수한 객체를 이터러블이라 함.
이터러블은 for...of 문으로 순회할 수 있으며 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용 할 수 있음.
이터레이터 프로토콜
이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환.
이터레이터 리절트 객체를 반환. 이터레이터 프로토콜을 준수한 객체를 이터레이터라 함.
const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';
// 배열, 문자열, Map, Set 등은 이터러블이다.
isIterable([]); // -> true
isIterable(''); // -> true
isIterable(new Map()); // -> true
isIterable(new Set()); // -> true
isIterable({}); // -> false
const array = [1, 2, 3];
// 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in array); // true
// 이터러블인 배열은 for...of 문으로 순회 가능하다.
for (const item of array) {
console.log(item);
}
// 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다.
console.log([...array]); // [1, 2, 3]
// 이터러블인 배열은 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.
const [a, ...rest] = array;
console.log(a, rest); // 1, [2, 3]
const obj = { a: 1, b: 2 };
// 스프레드 프로퍼티 제안(Stage 4)은 객체 리터럴 내부에서 스프레드 문법의 사용을 허용한다.
console.log({ ...obj }); // { a: 1, b: 2 }
Symbol.iterator 메서드가 반환한 이터레이터는 next메서드를 갖는다.
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];
// Symbol.iterator 메서드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();
// Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다.
console.log('next' in iterator); // true
이터레이터 리절트 객체를 반환.
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];
// Symbol.iterator 메서드는 이터레이터를 반환한다. 이터레이터는 next 메서드를 갖는다.
const iterator = array[Symbol.iterator]();
// next 메서드를 호출하면 이터러블을 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를
// 반환한다. 이터레이터 리절트 객체는 value와 done 프로퍼티를 갖는 객체다.
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
for(변수선언문 of 이터러블){...}
for(변수선언문 in 객체){...}
for(const item of [1,2,3]){
console.log(item);
}
// 이터러블
const iterable = [1, 2, 3];
// 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다.
const iterator = iterable[Symbol.iterator]();
for (;;) {
// 이터레이터의 next 메서드를 호출하여 이터러블을 순회한다. 이때 next 메서드는 이터레이터 리절트 객체를 반환한다.
const res = iterator.next();
// next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true이면 이터러블의 순회를 중단한다.
if (res.done) break;
// 이터레이터 리절트 객체의 value 프로퍼티 값을 item 변수에 할당한다.
const item = res.value;
console.log(item); // 1 2 3
}
유사배열 객체는 마치 배열처럼 인덱스로 프로퍼티 인덱스로 프로퍼티 값에 접근 , length프로퍼티를 갖는 객체를 말함.
유사배열 객체는 length프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열을 프로퍼티 키로 가지므로 배열처럼 인덱스로 프로퍼티 값에 접근 할 수 있음.
// 유사 배열 객체
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};
// 유사 배열 객체는 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있다.
for (let i = 0; i < arrayLike.length; i++) {
// 유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있다.
console.log(arrayLike[i]); // 1 2 3
}
// 유사 배열 객체는 이터러블이 아니기 때문에 for...of 문으로 순회할 수 없다.
for (const item of arrayLike) {
console.log(item); // 1 2 3
}
// -> TypeError: arrayLike is not iterable
// 유사 배열 객체
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};
// Array.from은 유사 배열 객체 또는 이터러블을 배열로 변환한다
const arr = Array.from(arrayLike);
console.log(arr); // [1, 2, 3]
예를 들어 피보나치 수열을 구현한 간단한 사용자 정의 이터러블을 구현해보는 코드!
// 피보나치 수열을 구현한 사용자 정의 이터러블을 반환하는 함수. 수열의 최대값을 인수로 전달받는다.
const fibonacciFunc = function (max) {
let [pre, cur] = [0, 1];
// Symbol.iterator 메서드를 구현한 이터러블을 반환한다.
return {
[Symbol.iterator]() {
return {
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur >= max };
}
};
}
};
};
// 이터러블을 반환하는 함수에 수열의 최대값을 인수로 전달하면서 호출한다.
for (const num of fibonacciFunc(10)) {
console.log(num); // 1 2 3 5 8
}
// 이터러블은 스프레드 문법의 대상이 될 수 있다.
const arr = [...fibonacci];
console.log(arr); // [ 1, 2, 3, 5, 8 ]
// 이터러블은 배열 디스트럭처링 할당의 대상이 될 수 있다.
const [first, second, ...rest] = fibonacci;
console.log(first, second, rest); // 1 2 [ 3, 5, 8 ]
// 피보나치 수열을 구현한 사용자 정의 이터러블을 반환하는 함수. 수열의 최대값을 인수로 전달받는다.
const fibonacciFunc = function (max) {
let [pre, cur] = [0, 1];
// Symbol.iterator 메서드를 구현한 이터러블을 반환한다.
return {
[Symbol.iterator]() {
return {
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur >= max };
}
};
}
};
};
// 이터러블을 반환하는 함수에 수열의 최대값을 인수로 전달하면서 호출한다.
for (const num of fibonacciFunc(10)) {
console.log(num); // 1 2 3 5 8
}
// fibonacciFunc 함수는 이터러블을 반환한다.
const iterable = fibonacciFunc(5);
// 이터러블의 Symbol.iterator 메서드는 이터레이터를 반환한다.
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 5, done: true }
// 이터러블이면서 이터레이터인 객체. 이터레이터를 반환하는 Symbol.iterator 메서드와
// 이터레이션 리절트 객체를 반환하는 next 메서드를 소유한다.
{
[Symbol.iterator]() { return this; },
next() {
return { value: any, done: boolean };
}
}
// 이터러블이면서 이터레이터인 객체를 반환하는 함수
const fibonacciFunc = function (max) {
let [pre, cur] = [0, 1];
// Symbol.iterator 메서드와 next 메서드를 소유한 이터러블이면서 이터레이터인 객체를 반환
return {
[Symbol.iterator]() { return this; },
// next 메서드는 이터레이터 리절트 객체를 반환
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur >= max };
}
};
};
// iter는 이터러블이면서 이터레이터다.
let iter = fibonacciFunc(10);
// iter는 이터러블이므로 for...of 문으로 순회할 수 있다.
for (const num of iter) {
console.log(num); // 1 2 3 5 8
}
// iter는 이터러블이면서 이터레이터다
iter = fibonacciFunc(10);
// iter는 이터레이터이므로 이터레이션 리절트 객체를 반환하는 next 메서드를 소유한다.
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: 8, done: false }
console.log(iter.next()); // { value: 13, done: true }
무한수열을 간단히 구현한 코드.
// 무한 이터러블을 생성하는 함수
const fibonacciFunc = function () {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() { return this; },
next() {
[pre, cur] = [cur, pre + cur];
// 무한을 구현해야 하므로 done 프로퍼티를 생략한다.
return { value: cur };
}
};
};
// fibonacciFunc 함수는 무한 이터러블을 생성한다.
for (const num of fibonacciFunc()) {
if (num > 10000) break;
console.log(num); // 1 2 3 5 8...4181 6765
}
// 배열 디스트럭처링 할당을 통해 무한 이터러블에서 3개의 요소만 취득한다.
const [f1, f2, f3] = fibonacciFunc();
console.log(f1, f2, f3); // 1 2 3
지연평가
위 코드는 ES6의 Generator와 Iterator 프로토콜을 활용하여 무한 이터러블을 생성하는 예시입니다.
fibonacciFunc 함수는 이터레이터 객체를 반환합니다. 이 이터레이터 객체는 next() 메서드를 가지며, 호출될 때마다 [pre, cur] 값을 갱신하여 [cur, pre + cur] 값을 반환합니다. 이렇게 하면 매번 새로운 값이 생성되므로 무한 이터러블을 생성할 수 있습니다.
for...of 구문에서 fibonacciFunc()의 반환 값을 이터러블로 받아옵니다. 이 때, if (num > 10000) break; 조건문을 통해 10000 이상의 수를 생성하는 것을 막습니다. 따라서 console.log(num) 구문에서는 10000 이하의 피보나치 수열이 출력됩니다.
그리고 배열 디스트럭처링 할당을 통해 무한 이터러블에서 첫 번째, 두 번째, 세 번째 값을 가져와 f1, f2, f3에 할당합니다. 이 때, fibonacciFunc 함수는 무한 이터러블을 생성하므로, 이처럼 필요한 값을 취득하는 시점까지 계산이 연기됩니다.
이러한 방식으로 지연 평가를 활용하면, 필요한 시점까지 계산을 미루고, 불필요한 계산을 방지하여 프로그램의 효율성을 높일 수 있습니다.