Jun 개발노트

코드스피치 강의 ES6+ 4회차 정리

August 16, 2020

Generator

  1. 정의

    • function * : Generator 리터럴
    • Generator를 호출하면 Iterator 객체가 나온다(Iterator이면서 Iteratable한 객체를 반환)

      • for of 뒤에는 Iteratable한 객체가 와야하기 때문에 generator는 올 수 없다.
    • yield를 사용하면 next()룰 호출한것 처럼 value를 리턴한다(이러한 것을 서스펜스)

      • generator 함수에서 yield를 통해 나오면 done : false, generator 함수가 끝나면 done : true
      • 서스펜션이 일어나면서 Iterator result object를 반환 === next()
      • 서스펜션 : Statement은 중간에 멈출수 없는데 Statement을 멈추는 것을 서스펜션
    • Coroutine(코루틴)의 형태

      • 실행 후 중간에 멈춰서 나가고 다시 들어와서 반복적으로 실행
      • 여러번 진입하고(진입 위치가 달라진다 여러번 반환한다) 들어올때 중간부터 들어올수 잇다
      • Roution : 처음부터 들어가서 문 전체를 실행시키는 것 == 함수
    • 이터레이너터에서는 제어문(for / while) 까지 중단 => 제네레이터 서스펜션
  2. Iteratable과 차이(객체 상태에 대한 관리요소)

    • Iteratable

      • 스코프를 이용하여 자유변수를 사용하던지 인스턴스로 만들어서 필드로 관리
    • Generator

      • 지역변수와 매개변수로 관리한다.

        function*(max){
          let cursor = 0;
        
            while(cursor < max){
              yield cursor * cursor;cursor++;
            }
        };

추상화 루프

  1. 정의

    • 제네레이터의 지연실행 측면과 추상 루프화에 대해 설명
    • 루프문을 작성하는 것보다 이터러블 객체로 추상화하면 객체의 상태값을 이터레이터 객체가 가지고 있어서 루프를 반복실행할수 있다.
    • 루프의 역할이 줄어들며, 기준 루프는 제어문의 역학을 가지고 있는데, 이터러블은 자체에서 선택하므로 더 많은 결정권을 가진다.
    • 객체안에 루프안에 상태를 보관할테니까 알아서 실행만 하면 알아서 루프처럼 실행
    • 루프 개념에 대한 개념을 추상화하면 객체가 된다
  2. 루프

    • 이터레이션 : 똑같은 행위를 반복
    • 리컬시브 : 똑같은 행위를 반복할수 없고 반복하면서 행위를 평가

Complex recursive

  1. 단순한 배열을 루프인 경우는 간단히 이터레이션을 작성할 수 있다.

    • 모던브라우저에서는 무조건 이터러블 객체만을 받는다(리턴은 this 안에 이터레이터 객체가 있다.) 반드시 구현해야 한다

      const iter = {
      [Symbol.iterator](){return this},
      arr : [1,2,3,4],
      next(){
       return {
         done : this.arr.length === 0,
         value : this.arr.pop()
       }
      }
      } 
  2. 만약 복잡한 다층형 형태의 데이터 구조는 어떻게 할 것인가?

    {
       [Symbol.iterator](){return this;},
       data: [ {a : [1,2,3,4] , b : '-'}, [5,6,7], 8, 9]],
       next (){
        let v;
        while(v = this.data.shift()){
         // 데이터에 대한 평가만 이루어지는 것은 좋지 않다. 
         // 무한루프 방지 제한 조건을 추가(&& v count < 100)
         switch(true){
             case Array.isArray(v):
                 this.data.unshift(...v);
                 break;
             case v && typeof v == 'object':
                 for(var k in v) this.data.unshift(v[k]);
                 break;
             default:
                 return {value:v, done:false};
         }
        }
        return {done:true};
       }
    }
    
    
    // es6 버전 
    {
        [Symbol.iterator](){return this;},
        data:[{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9],
        next(){
            let v;
            while(v = this.data.shift()){
              if(!(v instanceof Object)) return {value:v};
              if(!Array.isArray(v)) v = Object.values(v);
              this.data.unshift(...v);
            }
            return {done:true};
        }
    }
  3. Class로 만들어서 처리(유지보수 하기 어려운 코드)

      const Compx = class{
           constructor(data){this.data = data;}
           [Symbol.iterator](){
              const data = JSON.parse(JSON.stringify(this.data));
              return {
                 next(){
                   let v;
                   while(v = data.shift()){
                      if(!(v instanceof Object)) return {value:v};
                      if(!Array.isArray(v)) v = Object.values(v);
                      data.unshift(...v);
                   }
                   return {done:true};
                 }
              }; 
            }
      };
      const a = new Compx([{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9]);
      console.log([...a]);
      console.log([...a]);
  4. 제네레이터를 활용한 코드

    • 이터레이터가 없으며, 상태값을 반환하는 것도 없다(제네레이터가 이터레이터를 만들어 주며, 상태값을 반환한다)
    • 단, 제네네이터를 호출해야 한다

      const Compx = class{
          constructor(data){this.data = data;}
          *gene(){
            const data = JSON.parse(JSON.stringify(this.data));
            let v;
            while(v = data.shift()){
                 if(!(v instanceof Object)) yield v;
                 else{
                   if(!Array.isArray(v)) v = Object.values(v);
                    data.unshift(...v); 
                 }
             } 
           }
      };
      const a = new Compx([{a:[1,2,3,4], b:'-'}, [5,6,7], 8, 9]);
      console.log([...a.gene()]);
      console.log([...a.gene()]);

      Abstract Loop

  5. 정의

    • 다양한 구조의 루프와 무관하게 해당 값이나 상황의 개입만 하고 싶은 경우
    • Complex recursive의 Generator는 목적이 바뀌면 루프가 변경되어야 한다.
    • 제어문을 재활용할 수 없으므로 중복정의할 수 밖에 없다.(중복정의 보다는 구조객체를 만들어서 재활용 할 수 없을까??)
    • 결국 제어문을 직접 사용할 수 없고 구조객체를 이용해 루프실행기를 별도로 구현

      (data, f)=>{
           let v;
           while(v = data.shift()){
             if(!(v instanceof Object)){
               // v에 대해서 어떤한 작업을 추가하려면 ????
               f(v);
             }else{
               if(!Array.isArray(v)) v = Object.values(v);
               data.unshift(...v);
             }
           }
       }
      
       // v의 작업을 추가하려면 해결책은 중복정의 밖에 없다. 
      (data, f)=>{
           let v;
           /// --- 루프 공통 골결 ---
           while(v = data.shift()){
             if(!(v instanceof Object)){ // 개별 구조 객체 1
               console.log(v)
               f(v);
             }else{  // 개별 구조 객체 2
               if(!Array.isArray(v)) v = Object.values(v);
               data.unshift(...v);
             }
           }
           /// --- 루프 공통 골결 ---
       }
  6. 루프 추상화 실행

    • 팩토리(선택기) + 컴포지트(각 선택기에 해당하는 객체) 패턴으로 처리

      // 오브젝트 처리
      const Operator = class{
        static factory(v){
          if(v instanceof Object){
              if(!Array.isArray(v)) v = Object.values(v);
              return new ArrayOp(v.map(v=>Operator.factory(v)));
          }else return typeof v === 'string' ? new StringOp(v) : new PrimaOp(v);
        }
        constructor(v){this.v = v;}
        operation(f){throw 'override';}
      };
      // 문자열 처리
      const StringOp = class extends Operator{
        constructor(v){super(v);}
        operation(f){for(let i = 0; i <this.v.length; i++){f(this.v[i]);}}
      }     
      // 기본값 처리 
      const PrimaOp = class extends Operator{
         constructor(v){super(v);}
         operation(f){f(this.v);}
      };
      
      // 배열 처리
      const ArrayOp = class extends Operator{
        constructor(v){super(v);}
        operation(f){
          for(const v of this.v) v.operation(f);
        } 
      };
      Operator.factory([1,2,3,{a:4, b:5},6,7]).operation(console.log)
    • 팩토리(선택기) + 컴포지트(각 선택기에 해당하는 객체) + ES6 Iterable

      const Operator = class{
        static factory(v){
          if(v instanceof Object){
          if(!Array.isArray(v)) v = Object.values(v);
          return new ArrayOp(v.map(v=>Operator.factory(v)));
              }else return new PrimaOp(v);
        }
        constructor(v){this.v = v;}
        *gene(){throw 'override';} 
      };
      const PrimaOp = class extends Operator{
         constructor(v){super(v);}
         *gene(){yield this.v;}
      };
      const ArrayOp = class extends Operator{
        constructor(v){super(v);}
        *gene(){for(const v of this.v) yield * v.gene();} 
      };
      for(const v of Operator.factory([1,2,3,{a:4, b:5},6,7]).gene()) console.log(v);
    • 위에 메소드는 복잡해서 별도로 선택하는 팩토리가 필요하지만, 일반적으로 선택할 수 있게 만들면 라우터가 된다.

      • 웹서버는 url 경우의 수만큼 처리해주는 것, 경우의 수를 설정해주면 등록된 애들중에서 찾아 그 등록된 애들을 보여준다.
      • 동적으로 평가하니까 일반화가 어렵다. 이런 경우는 애들을 선택적으로 팩토리가 만들어준다.
    • yeild 위임

      • yield : 내거를 반환하고 중지
      • yield * 제네레이터 함수 : * 다음에 있는 제네레이터의 yield를 다 실행시키고 다음으로 넘어감(yield 위임)

Lazy Execution

  1. 정의 : 최소한의 호출하며, 필요없는 계산을 하지 않는것

    const odd = function*(data){
      for(const v of data){
        console.log("odd", odd.cnt++);
        if(v % 2) yield v;
      }
    };
    odd.cnt = 0;
    for(const v of odd([1,2,3,4])) console.log(v);
     // 결과
     0
     'odd' 0
     1
     'odd' 1
     'odd' 2
      3
     'odd' 3
    
    
    const take = function*(data, n){
      for(const v of data){
      console.log("take", take.cnt++);
      if(n--) yield v; else break; }
    };
    take.cnt = 0;
    for(const v of take([1,2,3,4], 2)) console.log(v);
     // 결과
     0
     'odd' 0
     1
     'odd' 1
     2
     'odd' 2   
    
    for(const v of take(odd([1,2,3,4]), 2)) console.log(v);
    // 결과
    0
    0
    'odd' 0
    'take' 0
    1
    'odd' 1
    'odd' 2
    'take' 1
    3
    'odd' 3
    
    const Stream = class{
        static get(v){return new Stream(v);} // 팩토리
        constructor(v){
            this.v = v;
            this.filters = [];
        }
        add(gene, ...arg){ // 커링
          this.filters.push(v=>gene(v, ...arg)); return this;
        }
        *gene(){
            let v = this.v;
            for(const f of this.filters) v = f(v); yield* v;
       } 
     };
    
    const odd = function*(data){
    for(const v of data) if(v % 2) yield v;
    };
    const take = function*(data, n){
    for(const v of data) if(n--) yield v; else break; };
    
                           // 스트림 구현
    for(const v of Stream.get([1,2,3,4]).add(odd).add(take, 2).gene()) console.log(v);
    // 결과
    1
    2
  2. 정리

    • 스트림을 통해 제네레이터를 이어줄수 있다. 지연실행을 제어문으로 구현할 수 있다.
    • 함수형에서는 함수를 호출할때까지 지연시킬수 있었는데, 제네레이터는 yield를 통해서 지연시킬 수 있기 때문이다.

동기로직 플로우 컨트롤

  1. 프로그램이 한꺼번에 적재되고 실행이 되면서 플로우가 일자로 생기는데, 제어문을 통해 플로우 컨트롤을 해줌(동기 flow)
  2. 동기흐름에서 원래는 한번 실행을 시키면 실행환경에서 못 빠져나오는데, 코루틴을 활용해서 다른곳을 다시 갔다가 다시 오는게 가능하다.

참고

  1. 알고리즘 : 상태와 제어문을 통해 원하는 값을 얻는것
  2. 지연실행은 함수의 특권이다.

    • 함수가 실행되기 전까지는 제어문을 실행 시키지 않을 수 있다.(함수 호출 지연실행)
  3. 제네레이터도 yield를 통해 밑에 있는 제어문을 실행시키지 않을 수 있다.(코루틴)
  4. 자바스크립트에서 함수를 정의하는 방법

    • 함수선언문 : 일반적인 함수의 형태 호이스팅 발생
    • 함수표현식 : 함수를 변수에 할당해서 사용
    • 함수 선언보다 함수 또한 값이기 때문에 호이스팅에 의자하지 않고 값에 할당하여 사용(클래스(== 함수) 또한 하나의 값)
  5. 유지보수가 좋은 코드

    • 코드는 읽을 수 있는것, 주석은 오해를 불러일으키기 쉽다.
  6. JSON.stringify, JSON.parse는 그 어떤것 보다 빠르다(이유 : C로 처리함)
  7. IF를 제거하는 방법

    • IF로 나누어진 경우의 수 만큼의 값을 미리 만들어 놓고 바깥쪽에서 선택해서 들어오게 하면 IF 하나를 제거할 수 있다.
    • 이렇게 곁곁이 쌓으면 조건을 하나씩 제거 할 수 있다.
  8. 현대 언어들은 일반적으로 코드를 컴파일하고 돌리면 오퍼랜드(오피코드)로 바뀌어 컴퓨터를 넣어주면 flow control 불가

    • 오피코드를 객체로 감싸 다시 오피코드로 만들어야지 실행 중간에 명령을 선택할 수 잇게 한다 => 코루틴
  9. 최적화를 신경쓰지 말아라. 브라우저가 다 해준다.

    • 굳이 하고 싶다면, 주어진 내장객체(Object.assign, for-each…)의 함수를 사용하면 최적화 처리를 했기때문에 사용하는게 제일 좋다.
  10. %(모듈러)는 음의 정수에서는 작동하지 않기 때문에 Math.absolute를 활용하여 만들어줘야 한다.

Written by Junho You 배운것을 기록하자