みかづきブログ・カスタム

基本的にはちょちょいのほいです。

delegateを自作のものからnpmに乗り換える 🚗

f:id:kimizuka:20200917190940p:plain

この記事ではdelegateの説明は省略しますが、興味のある方は昔書いた記事を読んでいただけると幸いです。

これまでは自作したdelegate関数を使って、deleateを設定してきました。

自作のdelegate

function delegate(parent, eventName, selector, callback) {
  parent.addEventListener(eventName, function(evt) {
    (function checkTarget(target) {
      evt.delegateTarget = target;

      if (target.matches && target.matches(selector, parent)) {
        callback.call(target, evt);
      } else {
        if (target === parent) {
          return;
        } else {
          checkTarget(target.parentNode);
        }
      }
    })(evt.target);
  }, false);
}

が。最近、npmでdelegateを発見。

www.npmjs.com

早速、乗り換えようかと思いましたが、折角なのでコードを見比べてみることにしました。

npmのdelegate

closest.js

var DOCUMENT_NODE_TYPE = 9;

/**
 * A polyfill for Element.matches()
 */
if (typeof Element !== 'undefined' && !Element.prototype.matches) {
    var proto = Element.prototype;

    proto.matches = proto.matchesSelector ||
                    proto.mozMatchesSelector ||
                    proto.msMatchesSelector ||
                    proto.oMatchesSelector ||
                    proto.webkitMatchesSelector;
}

/**
 * Finds the closest parent that matches a selector.
 *
 * @param {Element} element
 * @param {String} selector
 * @return {Function}
 */
function closest (element, selector) {
    while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {
        if (typeof element.matches === 'function' &&
            element.matches(selector)) {
          return element;
        }
        element = element.parentNode;
    }
}

module.exports = closest;

delegate.js

var closest = require('./closest');

/**
 * Delegates event to a selector.
 *
 * @param {Element} element
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @param {Boolean} useCapture
 * @return {Object}
 */
function _delegate(element, selector, type, callback, useCapture) {
    var listenerFn = listener.apply(this, arguments);

    element.addEventListener(type, listenerFn, useCapture);

    return {
        destroy: function() {
            element.removeEventListener(type, listenerFn, useCapture);
        }
    }
}

/**
 * Delegates event to a selector.
 *
 * @param {Element|String|Array} [elements]
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @param {Boolean} useCapture
 * @return {Object}
 */
function delegate(elements, selector, type, callback, useCapture) {
    // Handle the regular Element usage
    if (typeof elements.addEventListener === 'function') {
        return _delegate.apply(null, arguments);
    }

    // Handle Element-less usage, it defaults to global delegation
    if (typeof type === 'function') {
        // Use `document` as the first parameter, then apply arguments
        // This is a short way to .unshift `arguments` without running into deoptimizations
        return _delegate.bind(null, document).apply(null, arguments);
    }

    // Handle Selector-based usage
    if (typeof elements === 'string') {
        elements = document.querySelectorAll(elements);
    }

    // Handle Array-like based usage
    return Array.prototype.map.call(elements, function (element) {
        return _delegate(element, selector, type, callback, useCapture);
    });
}

/**
 * Finds closest match and invokes callback.
 *
 * @param {Element} element
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @return {Function}
 */
function listener(element, selector, type, callback) {
    return function(e) {
        e.delegateTarget = closest(e.target, selector);

        if (e.delegateTarget) {
            callback.call(element, e);
        }
    }
}

module.exports = delegate;

https://github.com/zenorocha/delegate より引用

基本的な方針は大体同じですね。matchesを使った判定方法も大体同じです。
引数を含めてbindしているところは「なるほど」と思いました。
大きな違いとして、npmの方には、delegateで設定したコールバックの削除(destroy)が提供されていることです。
いままでdelegateで設定したイベントを剥がそうと思ったことはなかったですが、確かにあった方が便利そうです。

というわけで、これからはnpmのdelegateを使っていこうと思います。