Skip to content

[JavaScript] 红宝书整理--事件(从原生说到JQuery) #14

@zgfang1993

Description

@zgfang1993

事件流

  • 事件冒泡(常用)
  • 事件捕获(少用)
  • DOM事件流
事件冒泡 事件开始由最具体的元素接收,逐级向上传播到不具体的节点(文档) 元素 → html → document
事件捕获 不太具体的元素更早接收事件,最具体的元素最后接收事件document →html →具体元素
DOM事件流 事件捕获阶段 → 处于目标阶段 → 事件冒泡阶段捕获阶段不会涉及事件目标

事件处理程序

  • HTML事件处理程序(不推荐)
  • DOM0级事件处理程序
  • DOM2级事件处理程序
  • IE事件处理程序
  • 跨浏览器的事件处理程序

事件对象

  • DOM中的事件对象
  • IE中的事件对象
  • 跨浏览器的事件对象

事件类型

  • UI事件
  • 焦点事件
  • 鼠标与滚轮事件
  • 键盘与文本事件
  • 复合事件
  • 变动事件
  • HTML5事件
  • 设备事件
  • 触摸与手势事件

内存和性能

  • 事件委托
  • 移除事件处理程序

模拟事件

  • DOM中的是事件模拟
  • IE中的事件模拟

事件处理程序

1. HTML事件处理程序(不推荐)

<a href="" onclick="showMessage()"></a> 调用
<a href="" onclick="alert('clicked')"></a>  转义
缺点:
1. 时差问题。<a href="" onclick="try{showMessage();}catch(ex){}"></a>
2.	扩展事件处理程序的作用域链在不同的浏览器中会导致不同结果
3.	HTML和JavaScript代码紧密耦合。

2.DOM0级事件处理程序

通过JavaScript指定事件处理程序的传统方式:将一个函数赋值给一个事件处理程序属性。

必须要获取一个操作对象的引用。
优点:

  1. 简单
  2. 跨浏览器

每个元素都有自己的事件处理程序属性(onclick),将这个属性设置为一个函数,就可以指定事件处理程序。

var btn = document.getElementById("btn");通过文档对象获取按钮的引用
btn.onclick = function(){  指定onclick事件处理程序
	alert(1);
}

使用DOM0级指定的事件处理程序被认为是元素的方法
事件处理程序是在元素的作用域中运行 this引用当前元素

var btn = document.getElementById("btn");
btn.onclick = function(){
	alert(this.id); //"btn"
}

可以通过this访问元素的任何属性和方法
会在事件流的冒泡阶段被处理

删除事件处理程序属性的值
btn.onclick = null; //删除事件处理程序

3.DOM2级事件处理程序

指定事件处理程序:addEventListener(事件名,事件处理程序的函数,布尔值)
删除事件处理程序:removeEventListener(事件名,事件处理程序的函数,布尔值)
true:在捕获阶段调用事件处理程序
false:在冒泡阶段调用事件处理程序(默认)

通过addEventListener()添加的事件只能通过removeEventListener()来移除。
移除时传入的参数与添加处理程序时使用的参数相同。
(通过addEventListener()添加的匿名函数无法移除)

var btn = document.getElementById("btn");
btn.addEventListener("click",function(){
	alert(this.id);
},false);
btn.addEventListener("click",function(){
	alert(“可以添加多个事件处理程序”);
},false);
//移除匿名函数没有用
btn. removeEventListener ("click",function(){ 
	alert(this.id);
},false);
//有效
var btn = document.getElementById("btn");
var handler = function(){
	alert(this.id);
}
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);

优点:
1.可以添加多个事件处理程序,按照添加顺序触发。

4. IE事件处理程序

指定事件处理程序:attachEvent(事件名,事件处理程序的函数)
删除事件处理程序:detachEvent(事件名,事件处理程序的函数)

IE8及之前版本只支持事件冒泡,通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段

var btn = document.getElementById("btn");
btn.attachEvent("onclick",function(){
	alert(this === window); //true
})
匿名函数不能被移除

区别:
1.事件处理程序会在全局作用域中运行。
2.按照相反的添加顺序触发。

优点:
1.可以添加多个事件处理程序,按照相反的添加顺序触发。
支持IE事件处理程序的浏览器:IE Opera

5. 跨浏览器的事件处理程序

1.使用隔离浏览器差异的JavaScript库
2.自己开发(使用能力检测),只需关注冒泡阶段。

第一步:创建方法addHandler(元素,事件名称,事件处理程序函数)
第二步:创建方法removeHandler(元素,事件名称,事件处理程序函数)
第三步:创建对象EventUtil

var EventUtil = {
	addHandler:function(element,name,handler){
		if(element.addEventListener){ //DOM2级事件处理程序
			element.addEventListener(name,handler,false);
		} else if(element.attachEvent){  //IE事件处理程序
			element.attachEvent("on"+name,handler);
		} else {
			element["on"+name] = handler;	 //DOM0级事件处理程序
		}
	},
	removeHandler:function(element,name,handler){
		if(element.removeEventListener){    //DOM2级事件处理程序
			element.removeEventListener(name,handler,false);
		} else if(element.detachEvent){     //IE事件处理程序
			element.detachEvent("on"+name,handler);
		} else {
			element["on"+name] = null;	 //DOM0级事件处理程序
		}
	}
};
使用
var btn = document.getElementById("btn");
var handler = function(){
	alert(this.id);
};
EventUtil.addHandler(btn,"click",handler);
EventUtil.removeHandler(btn,"click",handler);

没有考虑到所有浏览器问题(IE中作用域问题)
DOM0级对每个事件只支持一个事件处理程序

事件对象

1.DOM中的事件对象

var btn = document.getElementById("btn");
btn.onclick = function(event){
	alert(event.type); //"click"
}
btn.addEventListener("click",function(){
	alert(event.type); //"click"
},false);

<a href="" onclick="alert(event.type)"></a>

Event属性和方法:触发的事件类型不一样,属性和方法不一样
具体看附件。
bubbles
cancelable
currentTarget
defaultPrevented
detail
eventPhase

preventDefault()
stopImmediatePropagation()
stopPropagation()
target
trusted
type
view

document.body.onclick = function(event){
  alert(event.currentTarget === document.body); //true
  alert(this === document.body);//true
  alert(event.target === document.body.getElementById("btn"));//true
}
//处理多个事件
var btn = document.getElementById("btn");
var handler = function (event) {
    switch (event.type){
      case "click":
          alert("click"); break;
      case "mouseover":
          event.target.style.backgroundColor = "red"; break;
      case "mouseout":
          event.target.style.backgroundColor = ""; break;

    }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
// preventDefault()阻止特定事件的默认行为 
//只有cancelable属性为true才可以
var link = document.getElementById("link");
link.onclick = function(event){
    event.preventDefault(); //阻止点击链接的默认行为
}
//stopPropagation()立即停止事件在DOM层次中的传播,取消进一步的事件捕获或冒泡。
var btn = document.getElementById("btn");
btn.onclick = function (event) {
    alert("clicked");
    event.stopPropagation(); //click事件不会传到document.body 避免触发注册在document.body上的事件处理程序
};
document.body.onclick = function (event) {
    alert("body clicked");
}
//eventPhase 确定事件当前正位于事件流的哪个阶段
1- 捕获阶段
2- 处于目标
3- 冒泡阶段
var btn = document.getElementById("btn");
btn.onclick = function (event) {
    alert(event.eventPhase); //2
};
document.body.addEventListener("click",function (event) {
  alert(event.eventPhase); //1
},true)

document.body.onclick = function (event) {
  alert(event.eventPhase); //3
}

只有在事件处理程序执行期间,event对象才存在;执行完即销毁。

2.IE中的事件对象

1.在使用DOM0级方式添加事件处理程序,event对象作为window对象的一个属性存在。
2.通过attachEvent()添加,会有一个event对象作为参数传入事件处理程序函数中。
3.通过HTML特性指定的事件处理程序,通过event变量来访问

//1. 使用DOM0级方式添加事件处理程序
var btn = document.getElementById("btn");
btn.onclick = function () {
  var event = window.event;
  alert(event.type); //"click"
};
//2. 通过attachEvent()添加事件处理程序
var btn = document.getElementById("btn");
btn.attachEvent("onclick",function (event) {
  alert(event.type); //"click"
})
//3. 通过HTML特性指定的事件处理程序
<a href="" onclick="alert(event.type)"></a>
//事件处理程序的作用域是根据指定他的方式来确定的,this不会始终等于事件目标。
var btn = document.getElementById("btn");
btn.onclick = function () {
  alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick",function (event) {
  alert(event.srcElement === this); //false
})

属性和方法
cancelBubble →DOM stopPropagation()
returnValue →DOM preventDefault()
SRCElement
type

//returnValue 取消给定事件的默认行为
var link = document.getElementById("link");
link.onclick = function(){
  window.event.returnValue = false; //阻止点击链接的默认行为
}
//cancelBubble立即停止事件在DOM层次中的传播,取消进一步的事件捕获或冒泡。
var btn = document.getElementById("btn");
btn.onclick = function () {
  alert("clicked");
  window.event.cancelBubble = true; //click事件不会传到document.body 避免触发注册在document.body上的事件处理程序
};
document.body.onclick = function () {
  alert("body clicked");
}

3.跨浏览器的事件对象

DOM和IE中的event对象不同,IE中event对象的全部信息和方法DOM对象中都有,实现方式不同。

var EventUtil = {
  getEvent:function (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) {
      if(event.stopPropagation){
        event.stopPropagation();
      }else{
        event.cancelBubble = true;
      }
  }
};
//使用
btn.onclick = function (event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
}

内存和性能

1.事件委托

事件委托:对事件处理程序过多问题的解决
利用事件冒泡,只指定一个事件处理程序就可以管理某一类的所有事件。
适合事件委托技术的事件:click、mousedown、mouseup、keydown、keyup、keypress

var links = document.getElementById("links");
EventUtil.addForceTouchHandler(links,"click",function (event) {
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    switch (target.id){
      case "doSomething":
           document.title = "i changed the document's title";
           break;
      case "goSomewhere":
          document.href = "http://www.baidu.com";
          break;
      case "sayHi":
          alert("hi");
          break;
    }
});

2. 移除事件处理程序

在不需要的时候移除事件处理程序
1.从文档中移除带有事件处理程序的元素时。
2.卸载页面的时候。

<div id="myDiv">
  <input type="button" id="btn">
</div>
    
var btn = document.getElementById("btn");
btn.onclick = function () {
    btn.onclick = null; //移除事件处理程序
    document.getElementById("myDiv").innerHTML = "processing...";
}

2.在卸载页面之前通过onunload事件处理程序移除所有事件处理程序。
事件委托优点(需要跟踪的事件处理程序越少,移除越容易)

说说JQuery中的事件

1. bind()

直接绑定在元素上

$("ul li").bind("click", function () {
    alert($(this).text());
})
  • 这里用了隐式迭代的方法,如果匹配到的元素特别多的时候,比如如果我在ul里放了50个li元素,就得执行绑定50次。对于大量元素来说,影响到了性能。
    但是如果是id选择器,因为id唯一,用bind()方法就很快捷了。
  • 对于尚未存在的元素,无法绑定。(动态加载进来的元素)

随着DOM结构的复杂化和Ajax等动态脚本技术的运用,有了较多的动态添加进来的元素,直接用JQ添加click事件会发现新添加进来的元素并不能直接选取到,在这里就需要用到事件委托方法,JQ为事件委托提供了live()、dalegate()和on()方法。

事件委托: DOM在为页面中的每个元素分派事件时,相应的元素一般都在事件冒泡阶段处理事件。在类似 body > div > a 这样的结构中,如果单击a元素,click事件会从a→div→body(即document对象)。因此,发生在a上面的单击事件,div和body元素同样可以处理。而利用事件传播(这里是冒泡)这个机制,就可以实现事件委托。
具体来说,事件委托就是事件目标自身不处理事件,而是把处理任务委托给其父元素或者祖先元素,甚至根元素(document)。

2. live()

$("ul.list li").live("click",function(){   });  
// jQuery 1.3

缺点

  • $()函数会找到当前页面中的所有li元素并创建jQuery对象,
    但在确认事件目标时却不用这个li元素集合,
    而是使用选择符表达式与event.target或其祖先元素进行比较,
    因而生成这个jQuery对象会造成不必要的开销;

  • 默认把事件绑定到$(document)元素
    (如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失)

  • 只能放在直接选择的元素后面,不能在连缀的DOM遍历方法后面使用,
    即$(" ul.list li ").live...可以,但$( ul.list").find("li").live...不行

  • 收集li元素并创建jQuery对象,但实际操作的却是$(document)对象.

解决:

  • 避免生成不必要的jQuery对象
(function($){
    $("#info_table td").live("click",function(){     });
})(jQuery);

前提:脚本必须是在页面的head元素中链接和(或)执行的
“早委托”的hack,即在$(document).ready()方法外部调用.live()。
这时候刚好document元素可用,而整个DOM还远未生成。这个匿名函数不会等到DOM就绪就会执行。

  • 为了避免事件冒泡造成的性能损失 jQuery 1.4
    $("li",$("ul.list")[0]).live("click",function(){ });
    “受托方”就从默认的$(document)变成了$("ul.list ")[0],解决了“事件传播链”过长
  • 为了解决无谓生成元素集合的问题,jQuery 1.4.2干脆直接引入了一个新方法
    delegate()。

3.delegate()

$("ul.list ").delegate("li","click",function(){ 
   });

使用场景:

  • 为DOM中的很多元素绑定相同事件;
  • 为DOM中尚不存在的元素绑定事件;

优点:

  • 直接将目标元素选择符("li")、事件("click")及处理程序与“受托方”$("ul.list ")绑定,
    不额外收集元素、事件传播路径缩短、语义明确;
  • 支持在连缀的DOM遍历方法后面调用,
    $("div ").find("#list").delegate... 支持精确控制

用事件委托时,如果注册到目标元素上的其他事件处理程序使用.stopPropagation()阻止了事件传播,那么事件委托就会失效。

4. on() off()

jQuery 1.7
其实是将以前的绑定事件方法作了统一

$('button').on('click',function(){});
$(div).on('click', 'button',function(){});

如果指定selector,则为事件委托;否则,就是常规绑定。

//delegate源码
  delegate: function( selector, types, data, fn ) {
   return this.on( types, selector, data, fn );
  }

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions