본문 바로가기

Study/JavaScript

Iterable

Before Start

iterable이란 단어는 '순회가 가능한'의 의미를 갖는다. 이 때 순회라는 것은 '여러 곳을 돌아다님'의 의미를 갖는다. 즉 여러 곳을 돌아다니는 것이 가능하다는 의미가 된다.

이터러블은 복합데이터가 가지고 있는 데이터 하나, 하나를 돌아다닌다. 이 데이터를 하나씩 방문하면서 어떤 처리를 할 수 있다면 iterable이라고 할 수 있을 것 같다는 느낌이 든다.

 

'데이터를 하나씩 방문하면서 어떤 처리를 한다'는 말을 들었을 때, 각 element에 콜백 함수를 실행하는 forEach나, 각 element가 어떤 조건이 맞는지 아닌지를 확인하고, 조건에 맞는 데이터만으로 이루어진 새로운 배열을 반환하는 filter 등 다양한 method가 떠올랐다. 그렇다면 배열과 맵, 셋은 iterable인가? 그렇다. 하지만 안심하기엔 이르다. 

Iteration Protocol(interface) | iterable protocoliterator protocol

Iteration Protocol은 ES6에서 추가된 사항 중 하나로, 어떤 문법 사항이 아니라 '규격사항'을 말한다. 이 프로토콜에는 iterable protocoliterator protocol이 있는데, 이 각각은 무엇을 만족하는 것이 이터러블이고, 이터레이터인지를 정해놓은 것이다. 이 프로토콜의 핵심은, 이터러블이 아닌 데이터 집합이, 해당하는 규격사항을 만족하게 되면 이터러블이 된다는 것이다. 

대표적으로 이터러블이 아닌 것은 { object }이다. object가 iterable이 아니라는 것은 꽤 혼란스러운 이야기인데, for ( key in obj ){ /*...*/ } 구문을 이용하면 key를 통해 object의 각 value에 접근할 수 있기 때문이다. 

 

let me = {name: 'jurassic', age: 23, hasBlog: true};
for(key in me) {
    console.log(me[key])
}

// jurassic, 23, true

 

나는 객체 iterable이 아니다라는 사실이 반직관적(counter intuitive)이라고 생각했다. 멀쩡하게 key를 순회하고 있는데 왜 iterable이 아니라는 건지 잘 이해가 안됐다. 그래서 이것저것 찾아보던 중에 왜 Object는 Iterable이 아닌가?를 읽어보게 됐다. 이 질문 내용에 대한 답변을 살펴보면 "object는 iterable일 필요가 없다" 라고 이야기 하고 있는데, 여기서 iterable을 재정의할 필요가 생긴다. JavaScript에서 Iterable이란 for...of 문, spread syntax를 사용할 수 있는 복합데이터를 일컫는다. => array, string, map, set. 즉 object는 for...of 문의 대상으로 사용될 수 없다. 위의 예제 코드의 me object에 for...in 대신 for...of를 작성하면 에러가 발생한다. 

 

object는 iterable일 필요가 없다는 의미는, for... in을 사용하면 되는데 굳이 for... of를 사용하려고 그 프로토콜을 준수하기 위한 코드를 추가로 작성할 필요가 없다는 의미이다. 그래서 실제로 object를 iterable로 만드는 경우는 많지 않다. 

 

+) for 반복문들에 어떤 차이가 있는 걸까? | for forEach for...in for...of 

Which is iterable?  

우선 앞서 살펴 본 iterable과 iterator 프로토콜을 만족하기만 하면 이터러블이 된다. 이 때 iterble 프로토콜을 만족한다는 것은, 어떤 복합 데이터가 [Symbol.iterator]라는 함수를 가지고 있고, 이 함수를 호출했을 때 iterator를 반환한다는 것이다. iterator 프로토콜을 만족한다는 것은 앞서 반환한 iterator에 next()라는 함수가 포함되어 있어, 그 함수를 이용해 순회(iteration)하는 것이 가능하다는 것을 의미한다. 

조금 복잡하지만, 어쨌든 for... of 라는 유용한 반복 연산문과 spread syntax가 있고 이들은 iterable만을 대상으로 하기 때문에 JavaScript에서 무엇이 이터러블이고, 무엇이 이터러블이 아닌지를 짚고 넘어가는 것이 좋다. 

Iterable ⭕ | for of

array, map, set, string 

Iterable ❌ | for in

object

Syntax 

Rest Parameters(...)

우리는 함수의 Rest parameters에서 이미 ...에 대해 알아보았다. 똑같은 얼굴을 하고 있는 spread와 rest는 완전히 다른 역할을 한다. spread에 대해 알아보기 전에 rest를 간단히 복습하고 넘어가자. rest parameter는 함수에서 지정된 인자 외에 전달된 여러개의 인자(rest parameters)를 하나의 배열에 집어 넣는 역할을 한다.

 

function sum(first, second, ...nums) {
  return nums; 
}

sum(1, 2, 3, 4, 5, 6); // nums => [ 3, 4, 5, 6 ]

Spread syntax(...)

Spread syntax (...) allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

spread 문법은 배열과 문자열 등과 같은 이터러블의 각 요소를 함수의 인자, 배열의 요소로 전달할 수 있다. 
객체 리터럴에서 spread 문법은 한 객체의 모든 프로퍼티(key + value)를 새로운 객체의 key-value 쌍으로 열거할 수 있따. 

 

Spread는 rest와는 전혀 다른 역할을 한다. spread 문법을 이용하면 iterable의 요소들을 함수의 argument, 배열의 element로 펼치거나, 어떤 객체의 properties를 새로운 객체의 properties로 펼칠 수 있다(expands). 이 때 각각 ( ...arguments ), [ ...elements ], { ...properties }를 사용한다. 

 

+) iterable이 아닌 데이터도, 객체에는 spread 할당을 할 수 있다 | { ...false }가 가능한 이유

 

[...false] // Type Error
{...false} // {}

 

/* fuction argument */

function add(a, b, c) {
  return a + b + c;
}

const nums = [1, 2, 3];

add(nums[0], nums[1], nums[2]) // 6
add(...nums) // 6, using spread 

/* array element */

const fruits1 = ['🍋', '🍑'];
const fruits2 = ['🍊', '🍏'];
all = [...fruits1, '🍇', ...fruits2]; // ['🍋', '🍑','🍇', '🍊', '🍏']

/* object property */
const me = { 
  name: 'hj',
  age: 22,
  contact: { email: 'hj@naver.com', phone: '123-4567-8900' },
};

const updated = { ...me, job: 'engineer' }; 
// {name: 'hj', age: 22, contact: {…}, job: 'engineer'}

 

spread syntax활용 | 이미 존재하는 key의 값 덮어쓰기 

 

spread syntax의 활용

Destructuring assignment syntax

'assignment'라는 단어에서 변수에 어떤 값을 할당하는 문법임을 추측해 볼 수 있다. 구조분해 할당이란 배열의 value나 객체의 property를 개별적인 변수에 할당하는 것을 가능하게 하는 표현식 문법이다. 배열과 객체에서 구조 분해 할당이 어떻게 사용되는지 예제 코드를 살펴보자. 

 

+) 리액트의 useState는 배열을 반환하는데, const [data, setData] = useState()는 useState의 첫번째 요소를 data라는 변수에, 두번째 요소를 setData라는 변수에 할당하는 것을 의미한다. 즉 구조분해 할당이 사용되는 예시이다. 

 

/*Array destructuring*/

const fruits = ['🍋', '🍑', '🍊', '🍏'];
const [lemon, peach, ...others] = fruits;
// now, you don't need to fruits[0] to get '🍋'
// lemon => '🍋'
// others => ['🍊', '🍏']

/*Object destructuring*/

// ex1) 
const user = {id: 42, isVerified: true};
const {id, isVerified} = user;
// now, you don't need to user['id'] or user.id to get 42
// id => 42

// ex2)
const taylor = { name: 'taylor', age: 22, job: 'singer' };

function print({ name, age, job }) {
  console.log(name);
  console.log(age);
  console.log(job);
}

print(taylor); // { name, age, job } = taylor => 'taylor', 22, 'singer'

 

'Study > JavaScript' 카테고리의 다른 글

Call stack & Task que  (0) 2022.04.01
Modularization  (0) 2022.03.31
Map & Set  (0) 2022.03.30
Array  (0) 2022.03.29
Object  (0) 2022.03.28