Skip to content

Latest commit

 

History

History
266 lines (203 loc) · 9.05 KB

File metadata and controls

266 lines (203 loc) · 9.05 KB

1. 타입과 값

목차

  • 7가지 내장 타입으로 바라보기
  • 자바스크립트에서의 객체
  • JSON 데이터 타입
  • Iterable, Iterator
  • Symbol.iterator
    • ES6의 Iterable/Iterator 프로토콜 맞추기, 그리고 Generator
    • iterator[Symbol.iterator]() == iterator, 그리고 Generator
    • 유사 배열
  • Promise

이 글에서는 함수형 프로그래밍에 대해 다루기 앞서 자바스크립트의 타입들과 ES6의 값들에 대해 다뤄봅니다. 자바스크립트의 타입과 값의 특성과 함수형 프로그래밍과 연관 있는 부분들을 살펴봅니다.

7가지 내장 타입으로 바라보기

자바스크립트에는 7가지 내장 타입이 있습니다.

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol

위 타입은 다시 크게 두 가지로 나눌 수 있습니다. 객체(object)와 객체가 아닌 것입니다. 위 7가지 타입에서 객체를 제외한 모든 값은 원시 값이며, 원시 값의 경우 인자로 전달하거나 할당할 때 항상 값 복사가 이루어지고, 객체의 경우는 항상 레퍼런스 사본을 만듭니다.

const log = console.log;

var a = 10;
function f1(a2) {
  a2 = 5;
  log(a2); // 5
  log(a); // 10
}
f1(a);

const obj = { value: 10 };
function f2(obj2) {
  obj2.value = 5;
  log(obj2); // { value: 5 }
  log(obj); // { value: 5 }
  log(obj == obj2); // true
}
f2(obj);

원시타입과 객체의 전달/할당이 위와 같이 이루어지는 것을 잘 인지하고 다루는 것이 중요합니다. 이는 자바스크립트로 함수형 프로그래밍을 할 때 중요하며, 특히 불변성을 다루는 기초가 됩니다.

자바스크립트에서의 객체

자바스크립트에서의 Array, Map, WeakMap, Set, WeakSet, Promise 등은 모두 object의 하위 타입입니다.

log(typeof {}); // "object"
log(typeof []); // "object"
log(typeof new Map); // "object"
log(typeof new Set); // "object"
log(typeof new Promise(a => a)); // "object"

JSON 데이터 타입

JSON 데이터 타입은 다음과 같습니다.

  • string
  • number
  • object
  • array
  • true, false, null

JSON은 경량의 DATA-교환 형식이면서, 현대 프로그래밍에서 가장 많이 사용되는 DATA-교환 언어입니다.

위 값에 해당하지 않는 모든 값은 JSON으로 직렬화가 불가능합니다. 예를 들면 undefined도 JSON으로 직렬화할 수 없습니다.

JSON.stringify(undefined);
// undefined <--- 이건 JSON String이 된 것이 아니라 함수가 실패해서 undefined가 리턴된 것입니다.

JSON.stringify({ a: undefined });
// "{}"

JSON.stringify([undefined]);
// [null]

Map이나 Set 역시 JSON으로 직렬화할 수 없습니다.

JSON.stringify(new Set([1,2,3]));
// {}
JSON.stringify(new Map([['a', 1]]));
// {}

이처럼 자바스크립트에서는 JSON으로 직렬화할 수 있는 값과 그렇지 않은 값으로, 다시 두 가지로 나누어 데이터를 바라볼 수 있습니다.

Iterable, Iterator

ES6에는 Iterable/Iterator에 대한 프로토콜이 있고, 그것은 ES6에서 매우 중요합니다. 자바스크립트에서 Iterable, Iterator, Symbol.iterator, Generator 등은 자바스크립트의 for...of 문이나 전개 연산자 등과 사용됩니다. 이것들은 ES6에서 함수형 프로그래밍을 하는 것에 있어서도 아주 중요한 역할을 합니다.

ES6의 String, Array, Map, Set 등은 아래와 같이 사용가능하도록 Symbol.iterator 프로퍼티를 가진 객체입니다.

for (const val of [1, 2]) log(val);
// 1
// 2

for (const val of new Set([3, 4])) log(val);
// 3
// 4

log( [...[1, 2], ...new Set([3, 4])] );
// [1, 2, 3, 4]

Symbol.iterator

ES6의 Iterable/Iterator 프로토콜 맞추기, 그리고 Generator

자바스크립트에서 for...of나 전개 연산자(...)와 잘 동작하려면 Symbol.iterator를 잘 사용해야합니다.

  1. iterable은 iterable[Symbol.iterator](); 메서드를 가져야하고, 결과는 iterator여야합니다.
  2. iterator는 iterator.next() 메서드를 가져야하고, 결과는 { value: someting, done: true/false } 여야 합니다.
log(String.prototype[Symbol.iterator]);
// ƒ [Symbol.iterator]() { [native code] }
log(Array.prototype[Symbol.iterator]);
// ƒ values() { [native code] }

const iterable1 = [1, 2];
const iterator1 = iterable1[Symbol.iterator]();

log(iterator1.next()); // {value: 1, done: false}
log(iterator1.next()); // {value: 2, done: false}
log(iterator1.next()); // {value: undefined, done: true}
log(iterator1.next()); // {value: undefined, done: true}

for (const val of iterable1) console.log(val);
// 1
// 2

function reverseIterator(list) {
  var cur = list.length;
  return {
    next: () => cur-- > 0 ?
      { value: list[cur], done: false } :
      { value: undefined, done: true }
  }
}

const iterator2 = reverseIterator([1, 2]);

log(iterator2.next()); // {value: 2, done: false}
log(iterator2.next()); // {value: 1, done: false}
log(iterator2.next()); // {value: undefined, done: true}

for (const val of reverseIterator([1, 2])) log(val);
// Uncaught TypeError: reverseIterator(...) is not a function or its return value is not iterable

iterator[Symbol.iterator]() == iterator, 그리고 Generator

  1. iterator도 iterator[Symbol.iterator]();를 갖도록 하고, iterator[Symbol.iterator]() == iterator라면 더욱 잘 만들어진 iterator라고 할 수 있습니다.
  2. 자바스크립트의 내장 iterables의 'iterator를 리턴하는 메서드'를 실행하여 얻은 iterator는 모두 iterator[Symbol.iterator]() == iterator와 같은 규약을 지키고 있습니다.
  3. Generator를 통해 만든 iterator 역시 iterator[Symbol.iterator]() == iterator을 지킵니다.
function reverseIterator(list) {
  var cur = list.length;
  return {
    [Symbol.iterator]: function() { return this; },
    next: () => cur-- > 0 ?
      { value: list[cur], done: false } :
      { value: undefined, done: true }
  }
}

for (const val of reverseIterator([1, 2])) log(val);
// 2
// 1

// Generator
function *reverseIterator(list) {
  var cur = list.length;
  while (cur--) yield list[cur];
}

for (const val of reverseIterator([1, 2])) log(val);
// 2
// 1

앞서 for (const val of reverseIterator([1, 2])) log(val);에서는 에러가 발생했지만, iterator에 [Symbol.iterator]()를 구현하여 for...of에서 정상동작하게 되었습니다. 3, 4, 5를 잘 응용하면 for...of와 잘 동작하는 것을 넘어, 데이터 순회에 대한 매우 재밌고 새로운 해법들을 만들 수 있어 매우 중요합니다.

function *valuesIter(obj) {
  for (const key in obj) yield obj[key];
}
const users = {
  5: { id: 5, name: 'YB' },
  19: { id: 19, name: 'BX' },
  27: { id: 27, name: 'MD' }
};
for (const val of valuesIter(users)) log(val.name);
// YB
// BX
// MD

유사 배열

유사 배열은 { 0: 10, 1: 20, length: 2 }와 같은 객체를 말합니다. 다음과 같은 유사 배열은 전개 연산자나 for...of와 사용될 수 없습니다.

const arrayLike = { 0: 10, 1: 20, length: 2 };
for (const val of arrayLike) log(val);
// Uncaught TypeError: arrayLike is not iterable

물론 유사 배열을 순회할 수 있게 하는 generator를 만든다면 for...of 에서 사용할 수 있겠지만, ES6부터는 iterable이 아닌 유사 배열을 사용하지 않는 방향으로 가야합니다. 자바스크립트 3rd party library의 값들 중에는 아직 iterable/iterator 프로토콜을 따르지 않는 유사 배열도 있지만, 모두 ES6의 프로토콜에 맞게 변경될 것입니다.

대표적인 유사 배열로는 arguments, NodeList 등이 있습니다. 최신 환경에서 해당 값들은 iterable이 되었습니다. [Symbol.iterator]()가 구현되어있어, for...of나 전개 연산자와 사용할 수 있습니다.

!function() {
  for (const val of arguments) log(val);
  // 3
  // 4

  log(1, 2, ...arguments);
  // 1 2 3 4
} (3, 4);

최신 브라우저의 열거 가능한(enumerable) 속성이 있는 값들은 Iterable/Iterator 프로토콜을 잘 따르고 있습니다.

Iterable/Iterator 프로토콜에 대한 재밌는 아이디어들은 이후 글에서 더 소개하겠습니다.

Promise

Promise는 비동기적으로 대기(Pending) / 성공(Fulfilled, resolve) / 실패(Rejected, reject)를 다루는 값입니다. ES6+에서는 Promise와 관련하여 new Promise, then, catch, race, Promise.all, Promise.resolve, Project.reject 등을 지원합니다. Promise를 활용한 다양한 사례는 이후 글에서 더 소개하겠습니다.

new Promise(function(resolve) {
  setTimeout(function() {
    resolve('hi');
  }, 1000);
}).then(function(val) {
  console.log(val); // 1초 뒤 hi
});

new Promise(function(resolve) {
  setTimeout(function() {
    resolve(Promise.reject('ho'));
  }, 2000);
}).then(function(val) {
  console.log(val); // 여기 오지 않음
}).catch(function(val) {
  console.error(val); // 2초 뒤 ho
});