[es6] Map.from 만들기

개요

es6에 추가된 Array.from은 굉장히 다재다능한 함수로 다양한 소스로부터 배열을 만들어냅니다. 기본적으로 배열 그 자체가 올 수 있으며, 각종 리스트와 이터러블, 이터레이터가 올 수 있습니다. 그에 비해 Map의 생성자는 키, 값 페어로 된 이차원 배열만 인자로 받아들일 수 있어 굉장히 불편합니다.
오브젝트를 받아들여 Map의 생성을 자동화하는 실무상의 필요를 강하게 느껴 이를 구현하기로 했습니다.

Map.from 구현전략

from함수가 받게될 인자에는 열거가능한 다양한 타입의 객체를 받아들여 Map을 만들 수 있게 합니다. 이에 앞서 우선 기본이 되는 Map의 생성자를 확인할 필요가 있습니다.

let test = new Map([['a', 1], ['b', 2]]);
console.log(test.get('a')) //1
console.log(test.get('b')) //2

전형적인 map의 생성인자인데 2차원배열을 받아들이고 있음을 알 수 있습니다. 이제 이를 커버하기 위한 구현전략을 짜보죠.

  1. 우선 저러한 key, value쌍을 반환하는 표준적인 메소드이름은 entry입니다. 따라서 entry를 갖고 있다면 그것을 넘겨주면 됩니다.
  2. 하지만 배열의 경우 entry()의 결과가 [인덱스, 값] 이 되어버려 위와 같은 예라면 [0, [‘a’,1′]], [1, [‘b’,2]] 와 같은 의도하지 않은 상황이 될 수 있습니다. 따라서 배열의 경우는 굉장히 까다롭게 평가하는 편이 좋겠죠.
  3. 이에 반해 다른 객체의 경우 우선 entry를 검사하여 있으면 그 결과를 반환해주는게 좋을 것입니다.
  4. 이터러블과 이터레이터는 이차원배열형태라면 그 형태로 반영하고 아니라면 [인덱스, 값] 형태로 적용해야할 것입니다.
  5. Array.from보다 더 강력하게 제네레이터도 반영해주는 것으로 하죠.
  6. entry가 없는 일반 객체라면 in으로 순회하면서 값을 할당해주는 방법이 있을 것입니다. 이 경우에도 Object.getOwnPropertySymbol을 이용해 빠짐없이 심볼을 챙겨줘야할 것입니다.

기초 구현

이제 위의 전략 5가지를 반영하여 코드로 만들어보죠. 앞서 전략은 위에서 아래로 생각이 흐르지만 실제 구현은 중복로직을 막기 위해 개념상의 최상위 컨텍스트로부터 차근차근 책임을 위임하는 구조로 갑니다.

  1. 최상위 계층은 제네레이터인데 그 이유는 제네레이터가 두번째 계층소속인 이터레이터를 만들어내기 때문입니다.
  2. 두번째 계층에서는 이터러블과 이터레이터가 있는데 이유는 이들은 결국 배열로 변환되기 때문입니다.
  3. 세번째 계층에서 배열을 종류별로 분리합니다. 즉 [[k,v], [k,v]..] 형태의 배열을 분리하여 적용합니다.
  4. 네번째 계층에서는 entry메소드를 통해 처리되는 경우를 다룹니다.
  5. 다섯번째 계층에서 일반 오브젝트를 변환하는 과정을 다룹니다.
  6. 이상의 계층에 걸리지 않는다면 빈 Map을 반환하게 됩니다.

구현은 굉장히 간단합니다(뻥입니다)

if(Map && !Map.from){
  Map.from = arg => {
    if(!arg) return new Map(); //null등을 초기에 제거

    //1 제네레이터처리
    if(typeof arg == 'function'){
       const iterator = arg();
       if(iterator && iterator.constructor === (function*(){})().constructor){
         arg = iterator; //arg자체를 이터레이터로 변환해줌
       }
    }

    //2 이터러블처리
    if(typeof arg[Symbol.iterator] == 'function'){
      let iterator = arg[Symbol.iterator]();
      if(typeof iterator.next == 'function'){
        arg = Array.from(arg); //arg자체를 배열로 변환해줌
      }
    //2 이터레이터처리
    }else if(typeof arg.next == 'function'){
       arg = Array.from(arg); //arg자체를 배열로 변환해줌
    }

    //3 2차원배열을 걸러냄
    if(Array.isArray(arg) && arg.length > 0){
      if(Array.isArray(arg[0]) && arg[0].length == 2){
        return new Map(arg); //바로 넣어주면 됨
      }
    }

    //4 엔트리메소드를 갖고 있음
    if(typeof arg.entry == 'function'){
       return new Map(arg.entries());
    }

    //5 일반 오브젝트의 경우
    if(typeof arg == 'object'){
      let map = new Map();
      for(let k in arg) map.set(k, arg[k]);
      for(let k of Object.getOwnPropertySymbols(arg)) map[k] = arg[k];
      return map;
    }

    //6 이도저도 아닌경우
    return new Map();
  }
}

결론

가볍게 Map.from을 만들어봤습니다. 그저 json일부를 Map으로 치환좀 편하게 하자정도의 니즈에서 출발해서 큰일이 되어버렸네요. 이제 es6가 태동하는 시기에 편의함수나 폴리필을 남발할때는 아닌거 같은데 실무에서 쓰려니 아무래도 도우미가 필요한건 어쩔 수 없나봅니다.

%d 블로거가 이것을 좋아합니다: