코드스피치 강의 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

  1. 정의

    • 다양한 구조의 루프와 무관하게 해당 값이나 상황의 개입만 하고 싶은 경우
    • 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)
              }
          }
          /// --- 루프 공통 골결 ---
      }
  2. 루프 추상화 실행

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

      // 오브젝트 처리
      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를 활용하여 만들어줘야 한다.

© 2023, Customized by Joon