当我用纯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. linux地址空间划分

    LDD讲的很明白了: Linux 是一个虚拟内存系统, 意味着用户程序见到的地址不直接对应于硬件使用的物理地址. 虚拟内存引入了一个间接层, 它允许了许多好事情. 有了虚拟内存, 系统重运行的程序可以 ...

  2. Backup App's data without rooting the phone

    First I'd like to let you know that my phone is Android 6.0 Marshmallow. So it works on the latest A ...

  3. Android IOS WebRTC 音视频开发总结(二四)-- p2p调用堆栈

    本文主要分析webrtc音视频点对点部分的代码结构,文章来自博客园RTC.Blacker,转载请说明出处. 前段时间在查一个偶尔断线的问题(这种问题最蛋疼,不好重现,只能凭经验去搞),所以理了下web ...

  4. 浅谈C++虚函数

    很长时间都没写过博客了,主要是还没有养成思考总结的习惯,今天来一发. 我是重度拖延症患者,本来这篇总结应该是早就应该写下来的. 一.虚函数表 C++虚函数的机制想必大家都清楚了.不清楚的同学请参看各种 ...

  5. EasyUI 后台管理系统

    基础功能版: 测试地址:http://dev.blueapp.cn/index.php/2014/07/03/101/ 用户名:admin密码:123456 有问题可一起探讨,源码后期将放出 一直未测 ...

  6. Jquery入门之----------选择器-------------

    Jquery最核心的组成部分就是选择器引擎.他继承了CSS的语法,可以对DOM元素的标签名.属性名.状态等进行快速.精确的选择,并且不必担心浏览器的兼容性.Jquery选择器除实现了基本的标签选择外, ...

  7. linux系统下将php和mysql命令加入到环境变量中的方法

    在Linux CentOS系统上安装完php和MySQL后,为了使用方便,需要将php和mysql命令加到系统命令中,如果在没有添加到环境变量之前,执行 “php -v”命令查看当前php版本信息时时 ...

  8. 详解Oracle临时表的几种用法及意义

    Oracle临时表可以说是提高数据库处理性能的好方法,在没有必要存储时,只存储在Oracle临时表空间中.希望本文能对大家有所帮助. 1 .前言 Oracle Logo 目前所有使用 Oracle 作 ...

  9. js 实现栈

    function Stack() { this.dataStore = []; this.top = 0; this.push=push; this.pop=pop; this.peek=peek; ...

  10. 史上最全Vim快捷键键位图(入门到进阶)

    经典版 下面这个键位图应该是大家最常看见的经典版了. 对应的简体中文版 其实经典版是一系列的入门教程键位图的组合结果,下面是不同编辑模式下的键位图. 入门版 基本操作的入门版. 进阶版 增强版 下图是 ...