首页 > 编程语言 >第十四篇 事件 - 事件流、事件处理程序、事件委托(代理)

第十四篇 事件 - 事件流、事件处理程序、事件委托(代理)

时间:2023-04-05 22:45:47浏览次数:36  
标签:function addEventListener 处理程序 冒泡 第十四 click 事件

by caix in 深圳

事件

JavaScript 与 HTML 之间的交互通过事件实现

事件 就是用户或浏览器自身执行的某种动作,比如点击、加载,鼠标移入移出等等

可以使用侦听器来预订事件

事件流

bb80c4d9bf17a990540f62feba03a626.png

DOM (文档对象模型) 结构 是一个树形结构,当一个 HTML元素 产生一个事件时,该事件会在 元素结点 与 根节点 之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程可称为 DOM 事件流

DOM 规范规定了事件流分为三个阶段:事件捕获 => 达到目标元素 => 事件冒泡

其中事件捕获是最先发生的,最晚发生的是事件冒泡

总之 事件流 描述的是 网页中 中接收事件的顺序

过程

元素事件响应在 DOM树 中是从顶层的 Window开始 “流向” 目标元素,然后又从目标元素 “流向” 顶层的 Window

因为 Window对象 是直接面向用户的,那么用户触发一个事件,如点击事件,肯定是从 window对象 开始的,所以自然就是先捕获后冒泡

阶段

97ffb36110ce4f95e4ecb471aa6084f5.png

事件流 包括三个阶段: 1、事件捕获阶段 2、处于目标阶段 3、事件冒泡阶段

1、事件捕获阶段

    捕获阶段是指事件响应从最外层的 Window 开始,逐级向内层前进,直到具体事件目标元素。在捕获阶段,不会处理响应元素注册的冒泡事件

    该阶段的主要作用是捕获截取事件
    
2、处于目标阶段

    当事件不断的传递直到目标节点的时候,最终在目标节点上触发这个事件,就是目标阶段。具体的元素本身

    该阶段具有双重范围,即 捕获阶段的结束,冒泡阶段的开始
    
3、事件冒泡阶段  

    冒泡阶段与捕获阶段相反,事件的响应是从最底层开始一层一层往外传递到最外层的Window

    主要作用是将目标元素绑定事件执行的结果返回给浏览器。
    
    处理不同浏览器之间的差异,主要在该阶段完成
    
    我们平时用的事件绑定就是利用的事件冒泡的原理
模型

事件传播的顺序对应浏览器的两种事件流模型:捕获型事件流 和 冒泡型事件流

1、冒泡型事件流

    事件的传播是从最特定的事件目标到最不特定的事件目标。即从 DOM树 的叶子到根。【推荐】

2、捕获型事件流

    事件的传播是从最不特定的事件目标到最特定的事件目标。即从 DOM树 的根到叶子
    
    事件捕获的思想就是不太具体的节点应该更早接收到事件,而最具体的节点最后接收到事件
事件冒泡
微软提出了名为 事件冒泡(event bubbling) 的事件流,由内向外响应

1、阻止事件冒泡 

W3C标准 event.stopPropagation(); 不支持 IE9 以下版本

IE独有 event.cancelBubble = true;

 方法一 

   给子元素设置 .stopPropagation()

   function click(e){
   
      alert("我是最内层的div")
      
      var event = e || window.event
      
      event.stopPropagation()
      
      event.cancelBubble = true // IE
   }
   
   event.stopPropagation() 则只阻止事件往上冒泡,不阻止事件本身
   
 方法二
   
   event.target == event.currentTarget
   
   让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡   
   
   function click1(e) {
        var event = e || window.event;
        if (event.target == event.currentTarget) {
            alert("我是最外层的div");
            return false
        }
    }
    
    注意: 
    
      如果采用这种方法阻止事件冒泡,必须所有属于冒泡影响范围内的元素都采用这个方法
      
 2、阻止事件默认行为    
 
  方法一

    适用于普通浏览器 e.preventDefault() 最常用
    
    <body>
      <a href="">test</a>
       <script>
          var a = document.querySelector('a')
          a.addEventListener('click', function(e) {
              e.preventDefault() => 适用于普通浏览器
          }) 
       </script>
    </body>
    
  方法二
  
     适用于低版本浏览器(IE6/7/8)  e.returnValue
     
     <body>
      <a href="">test</a>
     <script>
        var a = document.querySelector('a')
        a.onclick = function(e) { 
            e.returnValue  => 适用于低版本浏览器
        }
     </script>
     </body>
     
  方法三
  
      使用 return false 阻止默认行为 
      
      没有兼容性问题 但 return后面 代码不再执行
      
      <body>
        <a href="">test</a>
        <script>
          var a = document.querySelector('a')
          a.onclick = function(e) {
             return false
          }
        </script>
      </body>
事件捕获(event capturing)
网景提出了名为 事件捕获(event capturing) 的事件流,由外向内响应

事件代理(事件委托)

事件代理就是利用 事件冒泡 或 事件捕获 的机制把一系列的内层元素事件绑定到外层元素

当一个循环达到几十次或者上百次,并且在其本身绑定事件,渲染的时候,给每个 dom 绑定事件,这样操作是很耗费性能的,这时我们就应该利用冒泡的机制和事件流的特性去把他的事件绑定在父亲本身,这样我们只需要绑定一个事件就能操作所有元素

事件代理 可以减少事件处理器的数量,提高 JS 脚本的性能, 这也是前端性能优化的一个重要方面

事件代理的作用
减少监听器数量,去除重复笨重的代码

<ul id="item-list">
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
  <li>item4</li>
</ul>

let items = document.getElementById("item-list")

事件捕获实现事件代理

   items.addEventListener("click",(e) => {
     consle.log(e.target.innerHtml)
   },true)

事件冒泡实现事件代理

   items.addEventListener("click",(e) => {
     consle.log(e.target.innerHtml)
   },false)
<div id="box">
    <input type="button" id="add" value="添加" />
    <input type="button" id="remove" value="删除" />
    <input type="button" id="move" value="移动" />
    <input type="button" id="select" value="选择" />
 </div>
 
 window.onload = function(){
 
    var Add = document.getElementById("add");
    
    var Remove = document.getElementById("remove");
    
    var Move = document.getElementById("move");
    
    var Select = document.getElementById("select");
            
        Add.onclick = function(){
             alert('添加');
        };
        
        Remove.onclick = function(){
             alert('删除');
        };
        
        Move.onclick = function(){
             alert('移动');
        };
        
        Select.onclick = function(){
             alert('选择');
        }      
   }
   
上面实现的效果我就不多说了,很简单,4个按钮,点击每一个做不同的操作,那么至少需要4次dom操作,如果用事件委托,能进行优化吗 ?

window.onload = function(){

    var oBox = document.getElementById("box");
    
    oBox.onclick = function (ev) {
      var ev = ev || window.event;
      var target = ev.target || ev.srcElement;
      
     if(target.nodeName.toLocaleLowerCase() == 'input'){
           switch(target.id){
           case 'add' :
              alert('添加');
              break;
              
           case 'remove' :
              alert('删除');
              break;
              
            case 'move' :
              alert('移动');
              break;
              
            case 'select' :
              alert('选择');
              break;
          }
      }
    }       
  }
  
适合用事件委托的事件

click,mousedown,mouseup,keydown,keyup,keypress

值得注意的是,mouseover 和 mouseout 虽然也有事件冒泡

但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易

事件处理程序

事件意味着用户或浏览器执行的某种动作。比如,单击(click)、加载(load)、鼠标悬停 (mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)

事件处理程序的名字以“on”开头,且有很多方式来指定事件处理程序

HTML 事件处理程序

特定元素 支持的每个事件都可以使用事件处理程序的名字以 HTML 属性的形式来指定

将 JS 代码字符串赋值给 HTML 事件属性

<input type="button" onclick="console.log('clicked!')"/>

点击这个按钮后控制台会输出“clicked!”。这种交互能力是通过onclick属性指定 javascript代码 值来实现的

因为属性的值是 javascript 代码,所以不能以未转义的情况下使用HTML语法字符,如:和号(&)、双引号(")、小于号(<)、大于号(>)

为了避免使用HTML实体,可以使用单引号来代替双引号,或使用转义符号代替

在 HTML 中定义的时间处理程序可以包含精准的动作指令,也可以调用在页面其他地方定义的javascript脚本

<script>
   function showMessage() { console.log("Hello world!"); }
</script>
<button type="button" onclick="showMessage()">showMessage</button>

点击按钮时会调用 showMessage()函数

showMessage()函数 是单独在 <script> 元素中定义的,也可以在外部 js 文件中定义

以这种方式指定的事件处理程序有一些特殊的地方

1、首先,会创建一个函数来封装属性的值。这个 函数有一个特殊的局部变量 event,其中保存的就是 event 对象

<button type="button" onclick="console.log(event.type)">click me!</button>  => 输出 “click”

2、有了这个对象,就不用开发者另外定义其他变量,也不用从包装函数的参数列表中去取了。在这个函数中,this 值相当于事件的目标元素

<button type="button" onclick="console.log(this.type)">click me!</button> => 输出 “click”

3、这个动态创建的包装函数还有一个特别有意思的地方,就是其作用域链被扩展了。在这个函数中, document 和元素自身的成员都可以被当成局部变量来访问。这是通过使用 with 实现的 :

 function() {
    with(document) {
        with(this) {
         // 属性值 
      }
    } 
 }
 
 这意味着事件处理程序可以更方便地访问自己的属性。下面的代码与前面的示例功能一样
 
<button type="button" onclick="console.log(type)"> click me!</button> => 输出 “click”
DOM0 级事件处理程序

将一个函数赋值给一个事件处理程序属性

指以 Javascript 形式在DOM对象上以事件动作(如:onclick、onmouseover 等)为属性名指定事件处理程序

用法:

1、获取元素引用
let btn = document.getElementById("myBtn");

2、 添加事件
btn.onclick = function() {
    console.log("Hello world!");
};

3、 移除事件
btn.onclick = null;

作用域:

在元素的作用域中运行。换句话说,事件处理程序中的 this 引用当前元素

使用 DOM0 方式为事件处理程序赋值时,所赋函数被视为元素的方法。因此,事件处理程 序会在元素的作用域中运行,即 this 等于元素。

this 可以访问元素的任何属性和方法

以这种方式添加事件处理程序是注册在事件流的 冒泡阶段 的

let btn = document.getElementById("myBtn");
btn.onclick = function() {
    console.log(this.id); // "myBtn"
};

移除事件处理程序:

通过将事件处理程序属性的值设置为 null,可以移除通过 DOM0 方式添加的事件处理程序

btn.onclick = null; // 移除事件处理程序

缺点:

只能添加一个事件处理程序
DOM2 级事件处理程序

DOM2 级事件处理程序为事件处理程序的赋值和移除定义了两个方法:addEventListener() 和 removeEventListener()。

这两个方法暴露在所有 DOM 节点上,它们接收 3 个参数

使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
    console.log(this.id);
}, false);

与 DOM0 方式类似,这个事件处理程序同样在被附加到的元素的作用域中运行
<button id="myBtn">click me!</button>
<script>
  let btn = document.getElementById("myBtn");
  btn.addEventListener("click", () => { console.log(this.id); }, false);
  btn.addEventListener("click", () => { console.log("Hello world!"); }, false);
</script>

使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序
<div id="parent">
    <div id="child">
      I'm child
    </div>
</div>
<script>
    const parent = document.querySelector('#parent');
    const child = document.querySelector('#child');
    parent.addEventListener('click', function() {
      console.log('Im parent1');
    });
    parent.addEventListener('click', function() {
      console.log('Im parent2');
    }, true);
    child.addEventListener('click', function() {
      console.log('Im child1');
    });
    child.addEventListener('click', function() {
      console.log('Im child2');
    }, true);
</script>

这里分别给id为parent和child的div元素添加了一个冒泡阶段事件和一个捕获阶段事件

结合上述DOM事件流关系图可以推断出:当点击childdiv元素时,会先在捕获阶段触发parent的捕获阶段点击事件,然后触发child的捕获阶段点击事件,然后在冒泡阶段先触发child的冒泡阶段点击事件,最后触发parent的冒泡阶段点击事件。

打印结果为:

  1、Im parent2
  2、Im child2
  3、Im child1
  4、Im parent1
通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除

这意味着使用addEventListener()添加的匿名函数无法移除

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
    console.log(this.id);
}, false);

btn.removeEventListener("click", () => { // 没有效果
    console.log(this.id);
}, false);

由于传给 removeEventListener()函数 和传给 addEventListener()函数 的第二个参数为同一引用函数,所以会起到效果移除事件处理程序。
addEventListener()
新的语法

  target.addEventListener(type, listener [,{capture: Boolean, bubbling: Boolean, once: Boolean}]);

    type 表示监听事件类型的字符串
    
    listener 当所监听的事件类型触发时,会接收到一个事件通知
    
    options (可选)

      capture 表示listener会在该类型的事件捕获阶段传播到该EventTarget 时触发
      
      once 表示listener在添加之后最多只调用一次。如果是 true, listener会在其被调用之后自动移除
      
     passive 表示listener永远不会调用preventDefault()。如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
     
  <nav id="menu"><a href="https://juejin.cn">首页</a></nav>

  var nav = document.getElementById('menu')
  
  var link = nav.firstElementChild

capture:  

   表示 listener 会在该类型的事件捕获阶段传播到该EventTarget 时触发

  menu.addEventListener('click', function (e) {
     console.log('menu clicked!')
  }, { capture: true })  => menu clicked

  link.addEventListener('click', function (e) {
     e.preventDefault();
     console.log('link clicked!')
  }, { capture: false })  => link clicked
  
once: 

    表示listener在添加之后最多只调用一次
    
    如果是 true,listener会在其被调用之后自动移除 
    
    link.addEventListener('click', function (e) {
        e.preventDefault();
        console.log('link clicked!')
    }, { capture: false,once:true })

=> 输出一次link clicked 后自动移除 listener 函数,再次点击无效。

passive

   表示 listener 永远不会调用 preventDefault()
   
   如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告

    link.addEventListener('click', function (e) {
        e.preventDefault();
        console.log('link clicked!')
    }, {capture: false,passive:true })

=>  控制台输出:Unable to preventDefault inside 
passive event listener invocation. 

链接跳转

使用 passive 改善的滚屏性能,添加 passive 参数后,touchmove事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)

  addEventListener('touchmove', function listener() {
   /* do something */ }, { passive: true });

removeEventListener()
removeEventListener 删除对应 addEventListener 添加的事件,必须保证事件函数的引用地址为同一地址

因此匿名函数的注册事件无法被删除

el.addEventListener('click',()=>{},false);

el.removeEventListener('click',()=>{},false); => 无法删除添加的点击事件


let fun = () => {
    *****
}

el.addEventListener('click',fun,false);

el.removeEventListener('click',fun,false);
IE 事件处理程序

IE 实现了与 DOM 类似的方法,即 attachEvent()和 detachEvent()

这两个方法接收两个同样的参数:事件处理程序的名字 和 事件处理函数

因为 IE8 及更早版本只支持事件冒泡,所以使用 attachEvent() 添加的事件处理程序会添加到冒泡阶段

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
    console.log("Clicked");
});

注意:

attachEvent()的第一个参数是"onclick",而不是 DOM 的 addEventListener()方法 的"click"
在 IE 中使用 attachEvent()与使用 DOM0 方式的主要区别是事件处理程序的作用域。

使用 DOM0 方式时,事件处理程序中的 this 值等于目标元素。

而使用 attachEvent()时,事件处理程序是在全局作用域中运行的,因此 this 等于 window

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
    console.log(this === window); // true
});
与使用 addEventListener()一样,使用 attachEvent()方法也可以给一个元素添加多个事件处理程序

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
    console.log("Clicked");
});

btn.attachEvent("onclick", function() {
    console.log("Hello world!");
});

与DOM2方法不同的是,这里的事件处理程序会以他们定义的顺序反向触发

所以会先打印 Hello world!,然后打印 Clicked
使用 attachEvent()添加的事件处理程序将使用 detachEvent()来移除,只要提供相同的参数

与使用 DOM 方法类似,作为事件处理程序添加的匿名函数也无法移除

但只要传给 detachEvent() 方法相同的函数引用,就可以移除

var btn = document.getElementById("myBtn");

var handler = function() {
    console.log("Clicked");
};

btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);
跨浏览器事件处理程序

通过以上定义事件处理程序的概述,为了能够使浏览器兼容处理事件,可以封装如下代码

var EventUtil = {
    addHandler: function(element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, handler);
        } else {
        element["on" + type] = handler;
        }
    },
    
    removeHandler: function(element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    }
}

使用:

var btn = document.getElementById('myBtn');
var handler = function(){
    console.log('hi')
}

EventUtil.addHandler(btn,'click',handler);
EventUtil.removeHandler(btn,'click',handler);

标签:function,addEventListener,处理程序,冒泡,第十四,click,事件
From: https://www.cnblogs.com/caix-1987/p/17291200.html

相关文章

  • 第十五篇、事件 - 事件对象、事件类型、事件循环
    bycaixin深圳事件对象DOM中发生事件时,所有的相关信息都会被收集并存储在一个名为event的对象中这个对象包含了一些基本的信息,比如导致事件的元素,发生的事件类型,以及可能与特定事件相关的任何其他数据,所有的浏览器都支持这个对象,尽管支持的方式不同DOM在触发某个事件的时......
  • js 鼠标事件的位置x,y
    1.clientX和clientY与x,yclientX和clientY与x,y一样的,都是客户当前显示的屏幕上(反之可能被卷去)可视区域坐标,指鼠标的坐标,以浏览器显示网页区域的左上角开始,x,y是新浏览器支持2.offsetX,offsetYoffsetX,offsetY针对目标元素(就是被点击的元素)offsetXoffsetY是相对于触发元素不......
  • 事件监听
    JavaScriptHTMLDOM事件监听程序(w3school.com.cn)事件监听事件: HTML事件是发生在HTML元素上的“事情”。比如:按钮被点击鼠标移动到元素之上按下键盘按键事件监听: JavaScript 可以在事件被侦测到时执行代码 事件绑定  方法一&方法二:    ......
  • 异步编程之事件循环机制
    JavaScript是一门单线程语言,我们可以通过异步编程的方式来实现实现类似于多线程语言的并发操作。本文着重讲解通过事件循环机制来实现多个异步操作的有序执行、并发执行;通过事件队列实现同级多个并发操作的先后执行顺序,通过微任务和宏任务的概念来讲解不同阶段任务执行的先后顺序,......
  • 解决input中输入中文过程中会触发input事件的问题
    问题描述:监听文本输入框的input事件,在拼写汉字时会触发input事件,如下图:需求:选词完成后触发input事件,只触发一次。解决办法:通过查阅资料得知在输入中文(包括语音识别时)会先后触发compositionstart、compositionupdate、compositionend事件。触发compositionstart时,文本框会填入“虚......
  • 事件驱动
    事件驱动事件驱动系统呼应怎么当下业务博大以小发生每刻.spring能拿捏事件并能开发亻建app围绕他们,意味你app保持同步同你业务.spring有若干事件驱动项可选,从整合和串直到cloud功能和历流.事件驱动微服务当加上微服务,事件串打开兴奋机会-事件驱动架构就是一普通例子.spring......
  • 领域驱动设计(DDD)实践之路(二):事件驱动与CQRS
    vivo互联网技术微信公众号 作者:wenbozhang【领域驱动设计实践之路】系列往期精彩文章:《领域驱动设计(DDD)实践之路(一)》主要讲述了战略层面的DDD原则。这是“领域驱动设计实践之路”系列的第二篇文章,分析了如何应用事件来分离软件核心复杂度。探究CQRS为什么广泛应用于DDD项......
  • 详细介绍Glib 主事件循环轻度分析与编程应用
    glib是一个跨平台、用C语言编写的若干底层库的集合。编写案例最好能够结合glib源码,方便随时查看相关函数定义。glib实现了完整的事件循环分发机制。有一个主循环负责处理各种事件。事件通过事件源描述,常见的事件源文件描述符(文件、管道和socket)超时idle事件当然,也可以自......
  • 为什么 B 页面的 unload 事件在刷新后点击浏览器的返回按钮不触发?
    ......
  • md事件计数(sb_events)
    1.总体流程 sb的更新会先计算出events的值后(++或--),更新需要load的硬盘的sb属性(sb_loaded标志),之后统一提交bio到硬盘。值得一说的是,events计数并不一定是递增的,也可以回退。2.events计算1if(test_and_clear_bit(MD_SB_CHANGE_DEVS,&mddev->sb_flags))2fo......