みかづきブログ その3

3ヶ月つづけてみました。

delegateを仕上げよう 2017

ライブラリは、jQuery、Vue、React、PixiJSなどなど、
タスクランナーは、Grunt、Gulp、webpackなどなど、
これまで、いろんなものをつかってウェブフロントの開発を行ってきましたが、

最近Noライブラリ、Noタスクランナーで、
普通にJavaScriptを書くことが増えております。(余談ですがCSSは面倒すぎるのでCompassをつかっています)

で、先日、生のJavaScriptを書いていたとき、
「ここはデリゲートで処理しよう」と思ったの瞬間があったですが、
ふと、昔自作したデリゲート関数の存在を思い出しました。

kimizuka.hatenablog.com

kimizuka.hatenablog.com

kimizuka.hatenablog.com

(function(win, doc) {
    
    "use strict";
    
    var room = doc.querySelector("#room");
    
    delegate(room, "click", ".member", function() {
        alert(this.id);
    });
    
    function delegate(parent, eventName, selector, callback) {        
        parent.addEventListener(eventName, function(evt) {
           
            (function checkTarget(target) {
                if (target.matches(selector, parent)) {
                    callback.call(target);
                } else {
                    if (target === parent) {
                        return;
                    } else {
                        checkTarget(target.parentNode);
                    }
                }
            })(evt.target);

        }, false);
    }
    
})(this, document);

こちらが3年前につくったデリゲートの最終版のようだったので、
早速つかってみたのですが、

愕然としました。

おいおい。3年前の自分はこれで満足していたのかと。

そう。このコードには大きな問題点が隠されていたのです。

問題点1 targetにdocumentを渡すと、エラーを吐く

jQueryでいうところのlive的な処理を書くとエラーを吐きます。

 if (target.matches(selector, parent)) {

この部分、targetがmatchesを持っていることが期待されていますが、
documentはmatchesを持っていないからです。

解決策
 if (target.matches && target.matches(selector, parent)) {

という感じで、
targetがmatchesを持っていることを確認してから叩くというように改修しました。

問題点2 結局どの要素でイベントが発火したのかがわからない

evt.targetにはtargetで渡したDOMが入るので、結局どの要素でイベントが発火したのかがわからず困りました。

解決策
evt.delegateTarget = target;

evtにdelegateTargetというキーを追加して、イベントが発火したDOMを特定できるように改修しました。

問題点3 インデントがスペース4つ

時代を感じました。
3年前の僕はスペース4つ派だったようです。

解決策

いまの僕はスペース2つ派なので、インデントをスペース2つにしました。


完成したデリゲート関数

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);
}

いかがでしょう。
自分で使うぶんには困ることがなくなりました。
3年後にまた確認してみようと思います。