当我用纯CSS实现这个以后。我开始用JavaScript和样式类来完善功能。

  然后,我有一些想法,我想使用Delegated Events (事件委托)但是我不想有任何依赖,插入任何库,包括jQuery。我需要自己实现事件委托了。

  我们先来看看事件委托到底是什么?他们是怎么工作的,怎么去实现这种机制。

  好,它解决了什么问题?

  我们先看个简单的例子。

  先假设我们有一组按钮,我一次点击一个按钮,然后我希望被点中的状态设为"active"。再次点击时取消active。

  然后,我们可以写一些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开发经验,这个问题就很明显了。

  对于外行来说button变量是被封闭的,每次都会找到对应的button……但是其实这里只有一个button;每次循环都会被重新分配。

  第一个循环它指向第一个button,接下来是第二个。但当你点击时button变量永远只指向最后一个button元素,问题出在这。

  我们需要的是一个稳定的作用域;让我们重构一下。

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

  注* 上面这段代码结构有点复杂,也可以简单直接地使用一个闭包,封闭保存当前的button变量,如下所示:

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

  现在它能正常工作了。指向永远是正确的button

  那么这个方案有什么问题?

  这个方案看上去还可以,然而我们确实可以做得更好。

  首先我们创建了太多的处理函数。为每一个匹配的.toolbar button绑定了一个事件侦听和一个回调处理。假如只有三个按钮这种资源分配是可以忽略的。

  然而,如果我们有1000个呢?

<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>

  它也不会崩溃,但是这并不是最佳的方案。我们分配了大量不必要的函数。让我们重构一下,仅附加一次,即仅绑定一个函数(function),去处理这种有可能的数千次调用。

  相对于封闭button变量去存储当时我们点击的对象,我们可以使用event对象去获取当时点击的对象。

  event对象有一些元数据,在多次绑定的种情况下,我们可以使用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里是怎样传递的。

  事件的工作原理

  当用户点击一个元素时,一个事件就会被产生去通知用户当前的行为。事件在分发派遣时会有三个阶段:

  • 捕获阶段: Capturing
  • 触发阶段: Target
  • 冒泡阶段: Bubbling

  这个事件起始从document之前然后一路向下找到当前事件点击到的对象。当事件达到点击到的对象之后,它会按原路返回(冒泡过程),直到退出整个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>

当你单击Button 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元素。我们可以将我们的事件处理绑定到父元素上面,然后简化我们的解决方案,这个就叫事件的委托及代理(Delegated Events)。

  注* 其实Flash/Silverlight/WPF开发的事件机制是非常近似的,这里有一张他们的事件流程图。 除了Silverlight 3使用了旧版IE的仅有冒泡阶段的事件模型外,基本上也都有这三个阶段。(旧版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是非常低成本的一项选择。

  原文地址: codepen.io

理解JavaScript中的事件路由冒泡过程及委托代理机制的更多相关文章

  1. 理解JavaScript中的事件轮询

    原文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程,也 ...

  2. 深入理解javascript中的事件循环event-loop

    前面的话 本文将详细介绍javascript中的事件循环event-loop 线程 javascript是单线程的语言,也就是说,同一个时间只能做一件事.而这个单线程的特性,与它的用途有关,作为浏览器 ...

  3. 理解JavaScript中的事件处理 阻止冒泡event.stopPropagation();

    原文地址:http://www.cnblogs.com/binyong/articles/1750263.html 这篇文章对于了解Javascript的事件处理机制非常好,将它全文转载于此,以备不时 ...

  4. 理解Javascript中的事件绑定与事件委托

    最近在深入实践js中,遇到了一些问题,比如我需要为动态创建的DOM元素绑定事件,那么普通的事件绑定就不行了,于是通过上网查资料了解到事件委托,因此想总结一下js中的事件绑定与事件委托. 事件绑定   ...

  5. 再次理解javascript中的事件

    一.事件流的概念 + 事件流描述的是从页面中接收事件的顺序. 二.事件捕获和事件冒泡 +    事件冒泡接收事件的顺序:

  6. 理解JavaScript中的事件流

    原文地址:http://my.oschina.net/sevenhdu/blog/332014 目录[-] 事件冒泡 事件捕获 DOM事件流 当浏览器发展到第四代时(IE4和Netscape Comm ...

  7. 理解javascript中的事件模型

    javascript中有两种事件模型:DOM0,DOM2.而对于这两种的时间模型,我一直不是非常的清楚,现在通过网上查阅资料终于明白了一些. 一.  DOM0级事件模型 DOM0级事件模型是早期的事件 ...

  8. 彻底理解javascript 中的事件对象的pageY, clientY, screenY的区别和联系。

    说到底, pageY, clientY, screenY的计算,就是要找到参考点, 它们的值就是: 鼠标点击的点----------- 和参考点指点----------的直角坐标系的距离 stacko ...

  9. JavaScript 进阶教程一 JavaScript 中的事件流 - 事件冒泡和事件捕获

    先看下面的示例代码: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Jav ...

随机推荐

  1. dell n2000 组播抑制

    http://en.community.dell.com/support-forums/network-switches/f/866/t/19677497 http://en.community.de ...

  2. 【c实现,vc6调试通过】给出一字符串指针,计算出字符串指针中单词数

    #include <stdio.h> /* 给出一字符串指针,计算出字符串指针中单词数, 单词不包括'.',',',';','?','_','"',由0-9数字或26个字母组成 ...

  3. IE样式兼容写法

    1.第一种写法 利用<!--[if lt IE 6/7/8/9/10/11]><![endif]-->,给每个html写一个class <!DOCTYPE html> ...

  4. Unieap3.5-需要用到window.setTimeout的地方

    1.js大数据量操作且需要显示进度条 unieap.showLoading(true);//进度条 window.setTimeout(function(){ checkUtils.setAllRow ...

  5. trap在shell中捕捉信号

    一.trap捕捉到信号之后,可以有三种反应方式:(1)执行一段程序来处理这一信号(2)接受信号的默认操作(3)忽视这一信号 二.trap对上面三种方式提供了三种基本形式:第一种形式的trap命令在sh ...

  6. hdu 2255 奔小康赚大钱 KM算法

    看到这么奇葩的题目名我笑了,后来这么一个裸的KM调了2小时我哭了…… 这是个裸的KM算法,也没什么多说的,主要是注意多组数据时,每次都要把各种数组清空啊,赋值啊什么的,反正比较麻烦.至于为什么调了2小 ...

  7. Windowsphone本地应用信息与市场信息的获取

    本地信息都存放在 WMAppManifest 里面,获取就不用说了...知道位置 就知道怎么获取了.. 主要是讲那个 市场上面的详情怎么获取,就是API调用显示在这个页面里面的详情: public v ...

  8. Mvc多级Views目录

    一般我们在mvc开发过程中,都会碰到这样的问题.页面总是写在Views文件夹下,而且还只能一个Controller的页面只能写在相应的以Controller名命名的文件夹下.如果我们写到别处呢?那么肯 ...

  9. Win 2003下IIS6+Mysql+php5.2  isapi搭建 升级php5.2到5.3测试 借助fastcgi实现

    Win 2003下IIS6+Mysql+php5.2  原环境isapi搭建 升级php5.2到5.3测试 借助fastcgi实现 操作如下 实验前准备:php-5.3.5-Win32-VC6-x86 ...

  10. Uva 1588 Kickdown

    这道题思路并不难想,在做题过程中主要遇到的困难有: 因为没有仔细的考虑边界情况,没有分析全面,导致因=没有取到而得不出正确结果,浪费的大量时间. 今后在做这类题目时,一定要先进行一个比较全面的分析+模 ...