logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [functional JS ES6+] map, filter, reduce

    이미지 보기

    [functional JS ES6+] map, filter, reduce

    • 22.06.05 작성

    • 읽는 데 8

    TOC

    map

    map 함수?

    불필요한 for문을 제거하면서 동일한 기능을 하게 한다

    products = [
      {name: '바지', price: 25000},
      {name: '반팔티', price: 15000},
      {name: '긴팔티', price: 20000},
    ]
    
    // 기존 for 문
    let names = [];
    for (const p of products) {
      names.push(p.name);
    }
    log(names); // ["바지", "반팔티", "긴팔티"]
    
    
    // map 문
    const map = (f, iter) => {
      let res = [];
      for (const a of iter) {
        res.push(f(a));
      }
      return res;
    }
    
    log(map(p => p.name, products)); // ["바지", "반팔티", "긴팔티"]
    
    • 인자로 iterable을 받고, 함수도 받을 수 있다.
    • 함수로 iterable의 각 요소를 처리한 값들을 return에 반환
    • map 함수는 함수를 인자로 다루어 함수 내부에서 사용하기 때문에 고차함수
    • iterable protocol을 따르므로 높은 다형성을 가진다.

    map 함수의 높은 다형성

    log([1, 2, 3].map(a => a+1)); // [2, 3, 4]
    log(map(el => el.nodeName, document.querySelectorAll('*'))); // ["HTML", "HEAD", "SCRIPT", ...]
    
    document.querySelectorAll('*').map(el => el.nodeName); // TypeError
    
    • iterable이라면 map함수를 사용할 수 있다.
    • 하지만 iterable처럼 생긴 객체들에 대해 주의해야 한다.
    • 예를 들면 document.querySelectorAll을 통해 반환되는 NodeList는 array처럼 생겼으나, array를 상속받은 객체가 아니어서, map함수를 내장하고 있지 않아, 특별한 정의 없이 사용할 수 없다.
    • 결론 : iterable인 것이 반드시 map함수를 내장하고 있다는 것은 아니다!

    filter

    T/F 여부에 따라 걸러내는 함수

    // 기존 명령형
    const under20000 = [];
    for (const p of products) {
      if (p.price < 20000) under20000.push(p);
    }
    log(under20000); // [{name: '반팔티', price: 15000}]
    
    
    // filter 함수 사용
    const filter = (f, iter) => {
      let res = [];
      for (a of iter) {
        if (f(a)) res.push(a);
      }
      return res;
    }
    
    log(filter(p => p.price < 20000, products)); // [{name: '반팔티', price: 15000}]
    log(filter(n => n % 2, [1, 2, 3, 4])); // [1, 3]
    log(filter(n => n % 2, function *() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      yield 5;
    })) // [1, 3, 5]
    
    • filter 함수 역시 iterable protocol을 따르는 iterable을 조작 가능

    reduce

    iterable을 하나의 값으로 축약하는 함수

    // nums를 모두 더하는 경우
    const nums = [1, 2, 3, 4, 5];
    
    // 기존 방식
    let total = 0;
    for (const n of nums) {
      total += n;
    }
    log(total); // 15
    
    
    // reduce 방식
    const reduce = (f, init, iter) => {
      acc = init;
      for (const a of iter) {
        acc = f(acc, a);
      }
      return acc;
    }
    const add = (a, b) => a + b;
    log(reduce(add, 0, nums));
    
    • iterable을 모두 돌면서 인자로 주어지는 함수 f를 누적값과의 연산으로 처리
    • 최종적으로 모두 iterable을 돌면서 마무리되는 acc 값을 return

    • JS 내장 reduce는 init(초기값)을 생략하는 경우 iter의 첫번째 요소를 init으로 사용하도록 자동 설정
    const reduce = (f, acc, iter) => {
      if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
      }
      for (const a of iter) {
        acc = f(acc, a);
      }
      return acc;
    }
    

    reduce 질문 내용

    이 과정에서 이해가 되지 않아 질문을 올렸다가 이해해버려서 정정한 질문을 남긴다


    안녕하세요! 초기값 acc가 없는 경우 iter에서 첫 값을 acc로 지정하고 next() 메서드를 통해 두번째 값부터 f(acc, a)를 누적하는 로직에서 궁금한 점이 있어 질문 남깁니다.

    인자에 acc가 부재한 경우 if(!iter) 조건이 아니라 if(!acc) 조건으로 iter의 첫 값을 acc에 지정해주는 게 맞지 않나 싶었습니다. 해당 부분 시작 전에도 JS 내장 reduce 방식처럼 acc가 없는 경우 사용하는 방식이라고 소개하셔서 코드 부분에서 더 괴리가 있는 것 같습니다.

    또한 acc[Symbol.iterator](); 의 경우 acc는 초기값이고, iterator 프로토콜을 따르는 배열은 인자로 주어진 iter니까 iter[Symbol.iterator]()가 맞지 않나요?

    const reduce = (f, acc, iter) => {
      if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
      }
      ...
    }
    

    강의에서는 위와 같이 작성해주셨는데, 제가 생각했을 때 이해가 되는 코드는 아래와 같습니다.

    const reduce = (f, iter, acc) => {
      if (!acc) {
        iter = iter[Symbol.iterator]();
        acc = iter.next().value;
      }
      ...
    }
    

    acc가 없는 경우 인자가 2개가 들어온다고 생각하면, acc를 3번째 인자로 두어야 acc가 undefined라 !acc가 true 처리되어서 if문을 돌 것 같아서 순서를 바꿔보았습니다.

    제가 배움이 얕아 잘못 생각하고 있는 것이라면 어떤 부분에서 잘못 생각하고 있는건지 여쭙고 싶습니다. 감사합니다.

    __

    라고 생각했는데, 아예 iter랑 acc를 재지정해주셨다는 걸 알게 되었습니다. 다른 수강생들에게도 도움이 되기 위해 정리해보는데, 혹시 오류가 있다면 짚어주시면 감사하겠습니다.

    초기값(acc)이 함수로 전달되지 않는 경우 인자가 당겨져서 if 문 이전까지는 acc가 iterable(배열)이고, iter가 부재해undefined가 됩니다. 그러면 초기값이 없다는 if 문의 판별 조건은 (!iter)가 됩니다.

    if문 내부에서는 acc가 iterable이니 이를 iter로 다시 지정하는 것이고, acc는 단어 뜻 그대로 초기값으로 설정하기 위해 iter.next().value로 iterable의 첫 값으로 지정합니다.

    next()를 해서 iterable은 두 번째 값부터 for문에서 iteration을 진행하게 됩니다.

    iter와 acc 단어 자체의 의미에 집중하다보니 이해가 되지 않았는데, 당겨진다고 생각하니까 바로 납득이 되네요. 오류가 있다면 지적 부탁드립니다. 감사합니다!

    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다