まだ移譲が不足なのだろうか?

まだ移譲が不足なのだろうか?

<!DOCTYPE html>
<title>?</title>
<meta charset="utf-8">
<style>
label { color: green; margin: 1ex 1ex 1ex; font-weight: bold;}
</style>

<body>
<div>
  <label>KeyWord:</label>
  <input type="search" id="keyword" autofocus>
  <em>space で区切ることも可能</em>
  <ul id="list"></ul>
</div>
<script>

{
  class DelayWork {
    constructor (callBackFunc, wait = 1000) {
      this.callBackFunc = callBackFunc; // func.bind (this)
      this.wait         = wait;
      this.timerID      = null;
    }
    

    start (...arg) {
      if (! this.timerID) {
        let cbfunc = () => {
          this.timerID = null;
          this.callBackFunc (...arg);
        };
        this.timerID = setTimeout (cbfunc, this.wait);
      }
      return this;
    }
    
    
    stop () {
      if (this.timerID)
        clearTimeout (this.timerID);
      this.timerID = null;
      return this;
    }
  }
  


  //記事
  const Article_DEF_OPTION = { };
  
  class Article {
    constructor (title = '', text = '', date = new Date, keyword = title, option = { }) {
      if (1 > title.length)
        arguments.length;

      this.title  = title;
      this.text   = text;
      this.key    = keyword;
      this.date   = date;
      this.option = Object.assign ({ }, Article_DEF_OPTION, option);
    }
  }

  //_____________________________________________________

  const
    Book_DEF_OPTION = { },
    Book_REG_NORMALIZATION = /[-\/\\^$*+?.()|\[\]{}]/g,
    book_REG_SPLITER = /\s+/g,
    
    Book_createRegkey = (str) => {
      let [a, ...bc] = str.replace(Book_REG_NORMALIZATION, '\\$&').split (book_REG_SPLITER);
      return new RegExp (a + bc.map (s => `(?=.*${s})`), 'i'); //x(?=y0)(?=y1)(?=yn)...
    };


  //記事を本にまとめる。keyをもとに検索機能がある
  class Book {
    constructor (book = [ ], option = { }) {
      this.book    = book;
      this.option  = Object.assign ({ }, Book_DEF_OPTION, option);
      this.current = [ ]; //検索された記事
      this.key     = '';  //検索ワードの記憶
    }


    append (...args) {
      this.book.push (new Article (...args));
      return this;
    }
    

    load (ary) {
      ary.forEach (args => this.append (...args));
      return this;
    }
    
    
    search (key = '') {
      key = key.trim ();
      let len = key.length;

      if (len) {
        if (0 === this.key.length || len < this.key.length)
          this.current = this.book.slice ();
        
        if (key !== this.key) {
          let reg = Book_createRegkey (key);
          this.current = this.current.filter (rec => reg.test (rec.key));
          this.key = key;
        }
      }
      else {
        this.current = [ ];
        this.key = '';
      }

      return this;
    }

  }

  //_____________________________________________________
  

  //Book に表示機能を追加する
  const
    ExBook_DEF_OPTION = {
     disp_row  : 3, //一回の処理で表示する行
     disp_wait : 300, //次の表示処理までの時間
     disp_max  : 30 //最大の表示する行
    };


  class  ExBook extends Book {

    constructor (book = [ ], output, option = { }) {
      option = Object.assign ({ }, ExBook_DEF_OPTION, option);
      super (book, option);
      this.output = output;
      this.loop   = new DelayWork (this.outByBit.bind (this), option.disp_wait);
    }
    

    display (rows = this.current.slice ()) {
      this.cls ();
      this.outByBit (rows.splice (0, this.option.disp_max));
      return this;
    }

    
    cls () {
      this.loop.stop ();
      this.output.cls ();
      return this;
    }
    
    
    outByBit (rows = [ ]) {
      this.output.out (rows.splice (0, this.option.disp_row));
      if (rows.length)
        this.loop.start (rows);
      return this;
    }
    
  }

  //_____________________________________________________


  //Book に入力機能を追加する
  const
    Ex2Book_DEF_OPTION = {
      inkey_wait: 1000
    }


  class Ex2Book extends ExBook {

    constructor (book, input, output, option = { }) {
      option = Object.assign ({ }, Ex2Book_DEF_OPTION, option);
      super (book, output, option);
      this.input   = input;
      this.routine = new DelayWork (this.find.bind (this), option.inkey_wait);
      
      input.addEventListener ('input', this, false);
    }
    

    find (key = '') {
      key = key.trim ();

      if (key !== this.key)
        this.search (key).display ();

      return this;
    }
    

    handleEvent (event) {
      this.routine.stop ().start (event.target.value);
      return this;
    }

  }

  //_____________________________________________________

  class ListViewer {
    
    constructor (target) {
      this.target = target;
    }
    
    
    cls () {
      for (let p = this.target, e; e = p.firstChild; e.remove ());
      return this;
    }
    
    
    out (rows = [ ]) {
      let
        doc = this.target.ownerDocument,
        li    = doc.createElement ('li'),
        label = doc.createElement ('label'),
        span  = doc.createElement ('span'),
        fgm   = doc.createDocumentFragment ();
      
      li.appendChild (label);
      li.appendChild (span);
      
      this.target.appendChild (rows.reduce ((a, {title, text}) => {
        label.textContent = title;
        span.textContent  = text;
        fgm.appendChild (li.cloneNode (true));
        return a;
      }, fgm));
      
      return this;
    }
  }
  //_____________________________________________________

 
  this.Book       = Ex2Book;
  this.ListViewer = ListViewer;

}

//_____________________________________________________

const
  article = [
    { key: 'array map',    title: 'Array.map',    text: '配列から新たに作成した配列を返す'},
    { key: 'array filter', title: 'Array.filter', text: '配列から条件を満たす配列を返す'},
    { key: 'array some',   title: 'Array.some',   text: '配列から条件を一つでも満たすと true を返す'},
    { key: 'array reduce', title: 'Array.reduce', text: '配列評価しながら配列を返す'}
  ],
  DIC2 = `
    Array.prototype.push       配列の末尾に要素を追加する  array push
    Array.prototype.pop        配列の末尾の要素を削除する  array pop       
    Array.prototype.shift      配列の先頭の要素を削除する  array shift     
    Array.prototype.unshift    配列の先頭に要素を追加する  array unshift 
    Array.prototype.indexOf    要素のインデックスを取得する  array indexOf 
    Array.prototype.splice     インデックス位置を指定して要素を削除する  array splice  
    Array.prototype.slice      配列のコピー  array slice   
  `;

let 
  inp = document.querySelector ('#keyword'),
  out = new ListViewer (document.querySelector ('#list')),
  dic = new Book (article, inp, out);

dic.load (
  DIC2
  .trim ()
  .split (/\r|\n|\r|\n/)
  .map (s =>
    s.trim().split (/\t|\s{2,}/g)
     .map (s => s.trim ())
  )
);


</script>