JavaScript のイベント ルーティング バブリング プロセスと委任メカニズムを理解する

黄舟
リリース: 2017-02-27 14:05:52
オリジナル
1198 人が閲覧しました

純粋なCSSを使用してこれを実装した後。機能を完成させるために、JavaScript とスタイル クラスを使用し始めました。

それでは、いくつかアイデアがあるのですが、Delegated Events (イベント委任) を使用したいのですが、依存関係を持たず、jQuery を含むライブラリをプラグインしたくないのです。イベント委任を自分で実装する必要があります。

まずイベント委任とは何なのか見てみましょう?それらはどのように機能し、このメカニズムをどのように実装するか。

さて、それはどんな問題を解決するのでしょうか?

まずは簡単な例を見てみましょう。

一連のボタンがあるとします。一度に 1 つずつボタンをクリックし、クリックされた状態を「アクティブ」に設定したいとします。もう一度クリックするとキャンセルがアクティブになります。

次に、いくつかの HTML を書くことができます:

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>
ログイン後にコピー
ログイン後にコピー

上記のロジックはいくつかの標準 Javascript イベントで処理できます:

var buttons = document.querySelectorAll(".toolbar .btn");
for(var i = 0; i < buttons.length; i++) {
  var button = buttons[i];
  button.addEventListener("click", function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  });
}
ログイン後にコピー

見た目は良さそうですが、実際には期待どおりに動作しません。

クロージャの罠

JavaScript開発の経験があれば、この問題は明らかでしょう。

初心者のために説明すると、ボタン変数は閉じられており、対応するボタンが毎回見つかります...しかし、実際にはボタンは 1 つだけであり、ループするたびに再割り当てされます。

最初のループは最初のボタンを指し、次に 2 番目のボタンを指します。しかし、クリックすると、ボタン変数は常に最後のボタン要素を指します。これが問題です。

必要なのは安定したスコープです。それをリファクタリングしましょう。

var buttons = document.querySelectorAll(".toolbar button");
var createToolbarButtonHandler = function(button) {
  return function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  };
};

for(var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i]));
}
ログイン後にコピー

注* 上記のコードの構造は少し複雑ですが、以下に示すように、単純にクロージャを使用して現在のボタン変数を閉じて保存することもできます。

var buttons = document.querySelectorAll(".toolbar .btn");

for(var i = 0; i < buttons.length; i++) {
  (function(button) {
    button.addEventListener("click", function() {
      if(!button.classList.contains("active"))
        button.classList.add("active");
      else
        button.classList.remove("active");
    });
  })(buttons[i])
}
ログイン後にコピー

これで正常に動作するようになります。正しいボタンを指すことは常に

では、この解決策の何が問題なのでしょうか?

この解決策は問題ないように見えますが、実際にはもっと改善できるはずです。

まず、処理関数を作りすぎました。イベント リスナーとコールバック ハンドラーは、対応する各 .toolbar ボタンにバインドされます。ボタンが 3 つしかない場合、このリソース割り当ては無視できます。

しかし、1,000 個ある場合はどうなるでしょうか?

<ul class="toolbar">
  <li><button id="button_0001">Foo</button></li>
  <li><button id="button_0002">Bar</button></li>
  // ... 997 more elements ...
  <li><button id="button_1000">baz</button></li>
</ul>
ログイン後にコピー

クラッシュすることもありませんが、最善の解決策ではありません。無駄な機能をたくさん割り当てています。これをリファクタリングして、1 回だけアタッチし、1 つの関数のみをバインドして、潜在的に数千の呼び出しを処理できるようにしましょう。

閉じたボタン変数を使用してその時にクリックしたオブジェクトを保存するのではなく、イベントオブジェクトを使用してその時にクリックしたオブジェクトを取得できます。

イベント オブジェクトにはいくつかのメタデータがあります。複数のバインディングの場合、currentTarget を使用して、現在バインドされているオブジェクトを取得できます。

var buttons = document.querySelectorAll(".toolbar button");

var toolbarButtonHandler = function(e) {
  var button = e.currentTarget;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
};

for(var i = 0; i < buttons.length; i++) {
  button.addEventListener("click", toolbarButtonHandler);
}
ログイン後にコピー

悪くありません。ただし、これは単一の関数を単純化して読みやすくするだけですが、それでも複数回バインドされます。

しかし、もっと良くできる。

このリストにいくつかのボタンを動的に追加するとします。次に、これらの動的要素へのイベント バインディングも追加および削除します。次に、これらの処理関数で使用される変数と現在のコンテキストを永続化する必要がありますが、これは信頼性が低いように思えます。

もしかしたら他の方法もあるかもしれない。

まず、イベントがどのように機能し、DOM でどのように配信されるかを完全に理解しましょう。

イベントの仕組み

ユーザーが要素をクリックすると、現在の動作をユーザーに通知するイベントが生成されます。イベントがディスパッチされるときは 3 つのステージがあります: キャプチャ ステージ:

  • のキャプチャ ステージ: ターゲット 現在のイベントによってクリックされたオブジェクトを検索します。イベントがクリックされたオブジェクトに到達すると、DOM ツリー全体から出るまで元のパス (バブリング プロセス) に沿って戻ります。

  • これは HTML の例です:
  • <html>
    <body>
      <ul>
        <li id="li_1"><button id="button_1">Button A</button></li>
        <li id="li_2"><button id="button_2">Button B</button></li>
        <li id="li_3"><button id="button_3">Button C</button></li>
      </ul>
    </body>
    </html>
    ログイン後にコピー

    ボタン A をクリックすると、イベントのパスは次のようになります:

  • START
    | #document  \
    | HTML        |
    | BODY         } CAPTURE PHASE
    | UL          |
    | LI#li_1    /
    | BUTTON     <-- TARGET PHASE
    | LI#li_1    \
    | UL          |
    | BODY         } BUBBLING PHASE 
    | HTML        |
    v #document  /
    END
    ログイン後にコピー
  • 注、これは、イベントのパス上であなたをキャプチャできることを意味します イベントの場合クリックによって生成されると、このイベントが親要素 ul 要素を通過することは確実です。イベント処理を親要素にバインドして、ソリューションを簡素化できます。これは、イベントの委任とプロキシ (委任されたイベント) と呼ばれます。

  • 注* 実際、Flash/Silverlight/WPF によって開発されたイベント メカニズムは非常に似ています。これらのイベント フローチャートは次のとおりです。 Silverlight 3 では、バブリング ステージのみを備えた古いバージョンの IE のイベント モデルを使用していることを除けば、基本的にこれら 3 つのステージがあります。 (旧バージョンのIEやSL3のイベント処理は、イベント処理の仕組みを簡略化するためか、トリガーオブジェクトからルートオブジェクトへバブリングする処理しかありません。)

  事件委托代理

  委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。

  让我们看一个具体的例子,我们看看上文的那个工具栏的例子:

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>
ログイン後にコピー
ログイン後にコピー

  因为我们知道单击button元素会冒泡到UL.toolbar元素,让我们将事件处理放到这里试试。我们需要稍微调整一下:

var toolbar = document.querySelector(".toolbar");
toolbar.addEventListener("click", function(e) {
  var button = e.target;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
});
ログイン後にコピー

  这样我们清理了大量的代码,再也没有循环了。注意我们使用了e.target代替了之前的e.currentTarget。这是因为我们在一个不同的层次上面进行了事件侦听。

  • e.target 是当前触发事件的对象,即用户真正单击到的对象。

  • e.currentTarget 是当前处理事件的对象,即事件绑定的对象。

  在我们的例子中e.currentTarget就是UL.toolbar。

  注* 其实不止事件机制,在整个UI构架上FLEX(不是Flash) /Silverlight /WPF /Android的实现跟WEB也非常相似,都使用XML(HTML)实现模板及元素结构组织,Style(CSS)实现显示样式及UI,脚本(AS3,C#,Java,JS)实现控制。不过Web相对其他平台更加开放,不过历史遗留问题也更多。但是几乎所有的平台都支持Web标准,都内嵌有类似WebView这样的内嵌Web渲染机制,相对各大平台复杂的前端UI框架和学习曲线来说,使用Web技术实现Native APP的前端UI是非常低成本的一项选择。

  

 以上就是理解JavaScript中的事件路由冒泡过程及委托代理机制的内容,更多相关内容请关注PHP中文网(www.php.cn)!


ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート