clipboard.jsコード分析(3)-good-listener


前の記事ではclipboardを紹介しました.jsというツールライブラリの2番目の依存tiny-emitterは、主に簡易なイベントサブスクリプションパブリッシャを完了しています.今回は、clipboard.jsのソースコードの最後に依存する軽量ツールライブラリgood-listenerを紹介します.このツールライブラリは主にdomのイベントバインドをカプセル化し、イベント依頼delegateをサポートします.jqueryの書き方とよく似ています.ソースコードは簡潔で分かりやすく、イベントバインドモデルと原理を理解するのに役立ちます.
クイック使用法
const listen = require('good-listener')
good-listenerは、イベントをバインドするための一般的な3つの方法をサポートします.
  • nodeノードイベントバインド
  • var logo = document.getElementById('logo');
    
    listen(logo, 'click', function(e) {
        console.log(e);
    });
  • nodeList複数のノードがイベントバインディング
  • を巡回する.
    var anchors = document.querySelectorAll('a');
    
    listen(anchors, 'click', function(e) {
        console.log(e);
    });
  • 文字列形式の委任イベントバインド(デフォルトの委任オブジェクトdocument.body)
  • listen('.btn', 'click', function(e) {
        console.log(e);
    });

    コード実装good-listenerの実現は概ね以下の通りである.
    function listen(target, type, callback) {
      if (is.node(target)) {
        return listenNode(target, type, callback);
      } else if (is.nodeList(target)) {
        return listenNodeList(target, type, callback);
      } else if (is.string(target)) {
        return listenSelector(target, type, callback);
      } else {
        throw new TypeError(`argument must be a
        String, HTMLElement, HTMLCollection, or NodeList`);
      }
    }
    nodeおよびnodeListノードの実装は比較的簡単であり,ここでは主にdelegate委託の実装を分析する.
    Nodeノードイベントバインド
    1つの要素がnodeノードであるか否かを判断するには、構造関数constructorおよびnodeTypeの属性によって判断される.
    value !== undefined && value instanceof HTMLElement && value.nodeType === 1;
    listenNodeが実装され、オブジェクトが返され、イベントバインドにバインド解除方法destroyが拡張される
    function listenNode(node, type, callback) {
      node.addEventListener(type, callback);
    
      return {
        destroy: function() {
          node.removeEventListener(type, callback);
        }
      }
    }

    NodeList複数ノードループイベントバインド
    1つの要素がnodeListノードであるか否かを判断するには、構造関数constructorおよびlengthの属性によって判断される.クラス配列の要素が有効なdomノードであることを保証する必要がある
    var type = Object.prototype.toString.call(value)
    
    value !== undefined &&
        (type === '[object NodeList]' || type === '[object HTMLCollection]') &&
        ('length' in value) &&
        (value.length === 0 || exports.node(value[0]))
    listenNodeList実装、クラス配列を遍歴し、イベントバインドを一度に実行すればよい
    function listenNodeList(nodeList, type, callback) {
      Array.prototype.forEach.call(nodeList, function(node) {
        node.addEventListener(type, callback);
      });
    
      return {
        destroy: function() {
          Array.prototype.forEach.call(nodeList, function(node) {
            node.removeEventListener(type, callback);
          });
        }
      }
    }

    イベント依頼の実現
    イベント依頼を簡単に振り返ります.例えば、次のようなシーンがあります.

    1

    2

    3

    4

    pタグのクリックイベントを親要素divに依頼する必要があります.jqueryの書き方は
    $('.wrapper').on('click', '.delegate', function(e) {...})

    私たち自身が実現すれば、クリックしたtargetdelegateを含むかどうかを単純に判断することはできません.クリックした要素はサブ要素spanである可能性があるので、サブ要素は泡を立ててdelegateを見つけることができます.依頼もトリガーできるはずです.
    さらに、Elementのプロトタイプには、selectorの文字列を受け入れるmatchesメソッドがあり、elementの要素が指定する文字列が選択されると、trueに戻る.
    1.まずこの泡立ちの過程をシミュレートする
    // document  
    const DOCUMENT_NODE_TYPE = 9
    function closet (element, selector) {
        while(element && element.nodeType !== DOCUMENT_NODE_TYPE) {
            if (typeof element.matches === 'function' && element.matches(selector)) {
                return element
            }
            element = element.parentNode
        }
    }

    この関数は,クリックした要素が指定した委任要素に上向きに泡を立てることができるかどうかを判断するものである.上記の例を組み合わせるとspandelegateを上に見つけることができるかどうかです.
    2.エージェントモードを使用してイベントのコールバック関数に1層の判断ロジックをカプセル化し、依頼ロジックに合致した場合、コールバック関数を実行し、eventオブジェクトに属性delegateTargetを追加し、eventで対応する3つのオブジェクトを取得することができる.
  • thisおよびe.currentTargetは、委任要素
  • である.
  • e.delegateTargetは、委任要素
  • である.
  • e.targetはクリック要素
  • である.
    function listener(element, selector, type, callback) {
      return function(e) {
        e.delegateTarget = closest(e.target, selector);
    
        if (e.delegateTarget) {
          callback.call(element, e);
        }
      }
    }

    3.delegate内部パッケージ
    let _delegate = (element, selector, type, callback, useCapture) => {
      //  element selector    
      let listenerFn = listener.apply(this, arguments)
      element.addEventListener(type, listenerFn, useCapture)
      return {
        destroy () {
          element.removeListener(type, listenerFn, useCapture) 
        }
      }
    }

    4.delegateは2つのケースに分けられ、委任された要素はdom要素であってもよいし、cssセレクタ文字列であってもよい.以下に示すように
    delegate(document.body, '.btn', 'click', function(e) {
        console.log(e.delegateTarget);
    }, false);
    delegate('.container', '.btn', 'click', function(e) {
        console.log(e.delegateTarget);
    }, false);

    別々に処理すればよい
    let delegate = (elements, selector, type, callback, useCapture) => {
      if (typeof elements.addEventListener === 'function') {
        return _delegate.apply(null, arguments)
      }
    
      if (typeof elements === 'string') {
        elements = document.querySelectorAll(elements)
      }
      return Array.prototype.map.call(elements, function (element) {
        return _delegate(element, selector, type, callback, useCapture)
      })
    }

    締めくくり
    今回紹介したgood-listenerというツールライブラリの使い方とソースコード分析では、nodenodeListのイベントバインドパッケージの実装を紹介し、特に の実装方法を探求し、少ないコードで互換性の良いdelegateを実現しました.これでclipboard.jsの依存はすべて紹介され、次の文章ではコードを統合します.完全なclipboard.jsの実装を示します.