事件详解


事件流

  • 事件流描述的是从页面中接收事件的顺序
  • IE的事件流叫做事件冒泡,即时间开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)
  • 事件捕获 的思想是不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件
  • DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

事件处理程序

  • 事件就是用户或浏览器自身执行的某种动作。诸如click、load和mouseover都是事件的名字。而响应某个时间的函数就叫做时间处理程序(或事件侦听器)。事件处理程序的名字以on开头
  • 通过HTML指定事件处理程序有几个缺点:
    • 时差:用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能不具备执行的条件
    • 扩展事件处理程序的作用域链在不同浏览器会导致不同结果
    • HTML与JS紧密耦合,如果要更换时间处理程序,就要改动两个地方的代码
  • 事件处理函数的作用域是根据指定它的方式确定的
DOM0级事件处理程序
  • 取得操作对象的引用
  • 指定时间处理程序
  • 这个时候的事件处理程序是在元素的作用域中运行的;换句话说,程序中的this引用当前的元素
  • 删除事件处理程序:btn.onclick = null;
    var btn = document.getElementById("mybtn");
    btn.onclick = function() {
      alter(this.id);
    }
    
DOM2级事件处理程序
  • 用于处理指定和删除事件处理程序的操作:addEventListener()removeEventListener()
  • 三个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值。布尔值true表示在事件捕获阶段调用事件处理程序;false表示在事件冒泡阶段调用事件处理程序
  • 事件处理程序会按照添加它们的顺序触发
  • 要移除时间处理程序,传入removeEventListener中的事件处理程序函数必须与传入addEventListener中的相同
  • 大多数情况下,将事件处理程序添加到事件流的冒泡阶段,这样能最大限度地兼容各种浏览器
    var btn = document.getElementById("mybtn");
    var handler = function () {
      alert(this.id);
    }
    btn.addEventListener("click", handler, false);
    btn.removeEventListener("click", handler, false);
    
IE事件处理程序
  • attachEvent 和 detachEvent
  • 两个参数:事件处理程序的名字与时间处理程序函数
  • IE事件处理程序和DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;而IE会在全局作用域内运行,因此this等于window
    var btn = document.getElementById("mybtn");
    var handler = function () {
      alert("clicked");
    }
    btn.attachEvent("onclick", handler);
    btn.detachEvent("onclick", handler);
    

事件对象

  • 在触发DOM的某个事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息
  • this和event.target的区别:this是事件冒泡,动态变化。先触发内部事件,由内到外的执行;event.target代表的是触发事件的dom对象,是静态不变的;
DOM 中的事件对象
  • 在时间处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标;
    document.body.click = function (event) {
      console.log(event.currentTarget === document.body);//true
      console.log(this === document.body); // true
      console.log(event.target === document.getElementById("mybtn"));//true
    }
    
  • 属性及方法(只读)
    • bubbles:Boolean / 表明时间是否冒泡
    • cancelable:Boolean / 表明是否可以取消事件的默认行为
    • currentTarget:Element / 其事件处理程序当前正在处理事件的那个元素
    • defaultPrevented:Boolean /true表示已调用preventDefalut()
    • detail:Integer / 与时间相关的细节信息
    • eventPhase:Integer / 调用时间处理程序的阶段
    • preventDefault():function / 取消事件的默认行为
    • stopImmediatePropagation():function / 取消事件进一步捕获或冒泡,同时阻止任何事件处理程序被调用
    • stopPropagation():function / 取消事件进一步捕获或冒泡
    • target:Element / 事件的目标
    • trusted:Boolean / true表示时间时浏览器生成的,否则是通过js创建的
    • type:String / 触发的事件类型
    • view:AbstractView / 与事件相关联的抽象视图,等同于发生时间的window对象
IE中的事件对象
  • 访问方法
    • 在使用DOM0级方法的情况下:var event = window.event
    • 使用IE事件处理程序情况下:默认传入event参数
  • 属性和方法
    • cancelBubble:Boolean / r and w / 默认false, true取消事件冒泡
    • returnValue:Boolean / r and w / 默认为true, false可以取消事件的默认行为
    • srcElement:Element / r / 事件中的目标 = target(DOM事件)
    • type:String / r / 触发的事件类型

事件类型

  • UI事件
    • load:当页面完全加载后(包括所有图像,js文件,css文件等外部资源),就会触发window上面的load事件
    • unload:常用于清除引用,以避免内存泄露
    • resize:浏览器窗口大小变化时触发
    • scroll:window对象上发生的
  • 焦点事件
    • blur:不会冒泡,在元素失去焦点时触发
    • focus:元素获得焦点时触发,不会冒泡
    • focusin:元素获得焦点时触发,会冒泡
    • focusout:元素失去焦点时会触发
  • 鼠标与滚轮事件
    • mousedown:用户按下任意鼠标按钮
    • mouseup:用户释放鼠标按钮
    • click:单击鼠标或回车触发
    • dbclick:双击鼠标触发
    • mouseenter:鼠标从元素外部首次移动到元素范围内触发,不冒泡
    • mouseleave:位于元素上方的鼠标移动到元素范围之外触发,不冒泡
    • mousemove:鼠标在元素内部移动时重复地触发
    • mouseout:位于元素上方的鼠标移入到另一个元素触发,可能是外部元素也可能是后代元素
    • mouseover:位于元素外部的鼠标首次移入另一个元素边界之内触发
    • 区别:由于mouseenter/mouseleave不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其mouseover和mouseout事件,但是却不会触发mouseenter和mouseleave事件
    • (event.clientX, event.clientY):事件发生时鼠标指针在视口的水平和垂直坐标
    • (event.pageX, event.pageY):事件发生时鼠标指针在页面的水平和垂直坐标;页面没有滚动的情况下与客户区坐标的值相等
    • (screenX, screenY):事件发生时鼠标指针在屏幕的水平和垂直坐标
  • 键盘与文本事件
    • keydown:按下键盘的任意触发,按住不放反复触发
    • keypress:按下键盘的字符键/Esc触发,按住不放反复触发
    • keyup:释放键盘上的键时触发
  • HTML5事件
    • contextmenu事件,单击鼠标邮件调出上下文
    • beforeunload事件
    • DOMContentLoaded事件:形成完整的DOM树后触发,不理会图像、脚本、样式表等其他问价是否下载完
    • readystatechange事件,加载状态,document.readyState可能的值:
      • uninitialized
      • loading
      • loaded
      • interactive
      • complete
    • hashchange:URL参数列表及#后字符串变化时触发

事件委托

  • 内存和性能:JS中添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。原因是多方面的:① 每个函数都是对象,会占用内存;内存中的对象越多,性能越差;② 必须实现指定所有的事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间;
  • 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某个类型的所有事件

一个通用的跨浏览器事件函数

var EventUtil={
   addHandler:function(element,type,handler){ //添加事件
      if(element.addEventListener){
         element.addEventListener(type,handler,false);  //使用DOM2级方法添加事件
      }else if(element.attachEvent){//使用IE方法添加事件
         element.attachEvent("on"+type,handler);
      }else{
         element["on"+type]=handler;//使用DOM0级方法添加事件
      }
   },  
   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;
      }
   },
   getEvent:function(event){  //使用这个方法跨浏览器取得event对象
      return event?event:window.event;
   },
   getTarget:function(event){  //返回事件的实际目标
      return event.target||event.srcElement;
   },
   preventDefault:function(event){   //阻止事件的默认行为
      if(event.preventDefault){
         event.preventDefault();
      }else{
         event.returnValue=false;
      }
   },
   stopPropagation:function(event){  
     //立即停止事件在DOM中的传播
     //避免触发注册在document.body上面的事件处理程序
      if(event.stopPropagation){
         event.stopPropagation();
      }else{
         event.cancelBubble=true;
      }
   },
   getRelatedTarget:function(event){  //获取mouseover和mouseout相关元素
      if(event.relatedTarget){
         return event.relatedTarget;
      }else if(event.toElement){      //兼容IE8-
         return event.toElement;
      }else if(event.formElement){
         return event.formElement;
      }else{
         return null;
      }
   },
   getButton:function(event){    //获取mousedown或mouseup按下或释放的按钮是鼠标中的哪一个
      if(document.implementation.hasFeature("MouseEvents","2.0")){
         return event.button;
      }else{
         switch(event.button){   //将IE模型下的button属性映射为DOM模型下的button属性
            case 0:
            case 1:
            case 3:
            case 5:
            case 7:
               return 0;  //按下的是鼠标主按钮(一般是左键)
            case 2:
            case 6:
               return 2;  //按下的是中间的鼠标按钮
            case 4:
               return 1;  //鼠标次按钮(一般是右键)
         }
      }
   },
   getWheelDelta:function(event){ //获取表示鼠标滚轮滚动方向的数值
      if(event.wheelDelta){
         return event.wheelDelta;
      }else{
         return -event.detail*40;
      }
   },
   getCharCode:function(event){   //以跨浏览器取得相同的字符编码,需在keypress事件中使用
      if(typeof event.charCode=="number"){
         return event.charCode;
      }else{
         return event.keyCode;
      }
   }
};

函数绑定

  • 函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
    var handler = {
    message: "Event handled",
    handleClick: function (event) {
      alert(this.message);
    }
    }
    var btn = document.getElementById("myButton");
    EventUtil.addHandler(btn, "click", handler.handleClick);//undefined
    // 改进,使用闭包
    EventUtil.addHandler(btn, "click", function (event) {
    handler.handleClick(event);
    })
    // bind函数
    function bind(fn, context) {
    return function () {
      return fn.apply(context, arguments);
    }
    }
    EventUtil.addHandler(btn, "click", bind(handleClick, handler));
    // ES5原生
    EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
    

文章作者: Susie Chang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Susie Chang !
 上一篇
DOM详解(一) DOM详解(一)
参考资料 《JavaScript高级教程》 《高性能JavaScript》 节点层次Node类型及对应数值 Node.ELEMENT_NODE(1) Node.ATTRIBUTE_NODE(2) Node.TEXT_NODE(3) Nod
2018-10-11
下一篇 
ECMAScript6新特性 ECMAScript6新特性
参考资料 ECMAScript 6 入门 ECMAScript 6 vs ECMAScript 5 ES6新增的编程风格 let 块级作用域和const 全局常量 var命令存在变量提升效用,let命令没有这个问题 const 考虑到全局
2018-09-30
  目录