Event事件

客户端JavaScript程序采用了异步事件驱动编程模型。
 
一、相关术语
 
事件就是Web浏览器通知应用程序发生了什么事情。
 
事件类型(event type)是一个用来说明发生什么类型事件的字符串。例如,“mousemove”表示用户移动鼠标,“keydown”表示键盘上某个键被按下等等。
事件目标(event target)是发生的事件与之相关的对象。当讲事件时,我必须同时指明类型和目标。比如:window上的load事件或
在客户端JavaScript应用程序中,Window、Document和Element对象是最常见的事件目标。
事件处理程序(event handler)或 事件监听程序(event listener)是处理或响应事件的函数。
事件对象(event object)是与特定事件相关且包含有关该事件详细信息的对象。事件对象作为参数传递给事件处理程序函数(不包括IE8及之前版本,在这些浏览器中有时仅能通过全局变量event才能得到)。所有的事件对象都用来指定事件类型的type属性和指定事件目标的target属性。(在IE8及之前的版本中用srcElement而非target)
事件传播(event propagation)是浏览器决定哪个对象触发其事件处理程序的过程。当文档元素上发生某个类型的事件时,它们会在文档树上向上传播或“ 冒泡“(bubble)。
事件传播的另一种方式: 事件捕获(event capturing):在容器元素上注册的特定处理程序有机会在事件传播到真实目标之前捕获它。
 
二、Event事件
 
10.1注册事件处理程序
 
注册事件处理程序有两种基本方式:
(1)一种是给事件目标对象或文档元素设置属性。
按照约定,事件处理程序属性的名字由“on”后面跟着事件名组成:onclick、onchange等。这些属性名是区分大小写的,所有都是小写,即使是事件类型是由多个词组成的(比如“readystatechange”)。

"alert(1);">div>

 

window.οnlοad=function(){}

:尽量少用内联事件
 
事件处理程序属性的缺点是其设计都是围绕着假设每个事件目标对于每种事件类型将最多只有一个处理程序。
 
(2)另一种是通过 addEventListener()
window对象、Document对象和所有的文档元素(Element)都定义了一个名为addEventListener()方法,使用这个方法可以为事件目标注册事件处理程序。

target.addEventListener(type, listener[, useCapture]);

addEventListener方法接受三个参数。

type:事件名称(事件类型),字符串,大小写不敏感。 listener:监听函数。事件发生时,会调用该监听函数。 useCapture:布尔值,表示监听函数是否在捕获阶段(capture)触发,默认为false(监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。

使用addEventListener()方法时,事件类型不应包括前缀“on”,比如:“onclick”改成“click”等。

addEventListener('click',listener,false);

注意:调用addEventListener()并不会影响onclick属性的值。

 

var v = document.getElementById('mybutton');

v.onclick = function() {alert('1');}

v.addEventListener('click',function(){alert('2');},false);

上面的代码中,单击按钮会产生两个alert()对话框。
 
能通过多次调用addEventListener()方法为同一个对象注册同一事件类型的多个处理程序函数。所有该事件类型的注册处理程序都会按照注册的顺序调用。使用相同的参数在同一个对象上多次调用addEventListener()是没用的,处理程序仍然只注册一次,同时重复调用也不会改变调用处理程序的顺序(也就是说,如果为同一个事件多次添加同一个监听函数,函数只会执行一次,多余的添加将自动删除)。
 
相对addEventListener()的是 removeEventListener() 方法。
removeEventListener方法的参数,与addEventListener方法完全一致。它的第一个参数“事件类型”,也是大小写不敏感。
 
注意:removeEventListener()方法的事件处理程序函数必须是函数名。
 
dispatchEvent()
dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。

target.dispatchEvent(event)

dispatchEvent方法的参数是一个Event对象的实例。
 
在IE上
IE9以前的IE不支持addEventListener()和removeEventListener()。不过我们可以使用类似的方法attachEvent()和detachEvent();
 
attachEvent()detachEvent()方法的工作原理与addEventListener()和removeEventListener()类似,但有所区别:
  • 因为IE事件模型不支持事件捕获,所以attachEvent()和detachEvent()只有两个参数:事件类型和处理程序函数
  • IE方法的第一个参数使用了带“on”前缀的事件处理程序属性名。例如:当给addEventListener()传递“click”时,要给attachEvent()传递“onclick”
  • attachEvent()允许相同的事件处理程序函数注册多次。当特定的事件类型发生时,注册函数的调用次数和注册次数一样。
 
下面的代码中可以兼容ie浏览器:

//添加事件处理器

var addEvent = function(element, type, handler, useCapture) {   

  if(element.addEventListener) {   

    element.addEventListener(type, handler, useCapture ? true : false);   

  } else if(element.attachEvent) {   

    element.attachEvent('on' + type, handler);   

  } else if(element != window){   

    element['on' + type] = handler;   

  }   

};

//移除某个事件处理器

var removeEvent = function(element, type, handler, useCapture) {   

  if(element.removeEventListener) {   

    element.removeEventListener(type, handler, useCapture ? true : false);   } else if(element.detachEvent) {

     element.detachEvent('on' + type, handler);   

  } else if(element != window){   

    element['on' + type] = null;   

  }   

};

 
10.2 事件处理程序的调用
一旦注册了事件处理程序,浏览器就会在指定对象上发生指定类型事件时自动调用它。
 
10.2.1 事件处理程序的参数
通常调用事件处理程序时把事件对象(event)作为它们的一个参数。事件对象的属性提供了有关事件的详细信息。例如,type属性指定了发生的事件类型。
 
在IE8及以前的版本中,通过设置属性注册事件处理程序,当调用它们时并未传递事件对象。取而代之,需要通过全局对象window.event来获得事件对象。
下面的代码就是考虑了兼容性:

function handler(event){

  event = event || window.event;

}

 
10.2.2 事件处理程序的运行环境
当通过设置属性注册事件处理程序时,看起来就好像是在文档元素上定义了新方法:

e.οnclick=function(){}

事件处理程序在事件目标上定义,所以它们作为这个对象的方法来调用。也就是说,在事件处理程序内,this关键字指向事件目标。
 

10.2.3 事件处理程序的作用域

事件处理程序在其定义的作用域而非调用时的作用域中执行,并且它们能存取那个作用域中的任何一个本地变量。

 

10.2.4 事件处理程序的返回值

通过设置对象属性或HTML属性注册事件处理程序的返回值有时是非常有意义的。通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作。比如,表单提交按钮的onclick事件处理程序能返回false阻止浏览器提交表单。

v.onclick = function() {

  return false;

}

理解事件处理程序的返回值只对通过属性注册的处理程序才有意义。

 

10.2.5 调用顺序

文档元素或其他对象可以指定事件类型注册多个事件处理程序。当适当的事件发生时,浏览器必须按照下面的规则调用所有的事件处理程序:

  • 通过设置对象属性或HTML属性注册的处理程序一直优先调用。
  • 使用addEventListener()注册的处理程序按照它们的注册顺序调用。
  • 使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序。

 

10.2.6 事件传播

当事件目标是Window对象或其他一些单独对象(比如XMLHttpRequest)时,浏览器会简单的通过调用对象上适当的处理程序响应事件。

 

在调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到DOM树根。

 

 发生在文档元素上的大部分事件都会冒泡,但有些例外,比如focus、blur和scroll事件。文档元素上的load事件会冒泡,但它会在Document对象上停止冒泡而不会传播到Window对象。只有当整个文档都加载完毕时才会触发window对象的load事件。

 

当事件目标是文档或文档元素时,它会在不同的DOM节点之间传播(propagation)。

分为三个阶段:

  • 捕获阶段(capture phase):从window对象传导到目标对象。(window--document--....--目标对象)
  • 目标阶段(target phase):目标对象本身的事件处理程序调用。
  • 冒泡阶段(bubbling phase):从目标对象传导回window对象。(目标对象--父元素--....--document--window)

 

事件代理(事件委托)

基于事件会在冒泡阶段向上传播到父节点,我们可以将子节点的监听事件定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

"div">   

 

"item">123
  

 

document.getElementById('div').addEventListener('click', function(e) {   

  var target = e.target;   

  if(target.getAttribute('id').toLowerCase() == 'item') {   

    alert(1);   

  }   

});

如果使用事件代理,以后插入的新节点仍然可以监听的到。

 

如果使用JQuery,我们要为新增节点添加事件,除了在新增事件后添加事件外,还可以用下面的代码:

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

这种方式其实就是使用了事件代理。

 

10.2.7 事件取消

用属性注册的世界处理程序的返回值能用于取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也能通过调用事件对象的preventDefault()方法取消事件的默认操作。

在IE9之前的IE中,可以通过设置事件对象的returnValue属性为false来达到同样的效果。

function cancelHandler(event){

  var event = event || window.event;

  if(event.preventDefault) {event.preventDefault();}  //标准

  if(event.returnValue) { event.returnValue = false;}  // IE

  return false; //用于处理使用对象属性注册的处理程序

}

 

Event对象提供了一个属性defaultPrevented,返回一个布尔值,默认false,表示该事件是否调用过preventDefault方法。

 

取消事件传播

在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法以阻止事件的继续传播。

e.stopPropagation()

//IE

e.cancelBubble = true;

 

在Event对象上还有一个方法stopImmediatePropagation(),阻止同一个事件的其他监听函数被调用。也就是说,如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。

e.addEventListener('click',function(event){

  event.stopImmediatePropagation();

});

e.addEventListener('click',function(event){   

  //不会触发

});

 

10.3 文档事件

(1)beforeunload事件、unload事件、load事件、error事件、pageshow事件、pagehide事件

 

beforeunload

当浏览器将要跳转到新页面时触发这个事件。如果事件处理程序返回一个字符串,那么它将出现在询问用户是否想离开当前页面的标准对话框中。

window.addEventListener('beforeunload',function(e){

  var message = '你确认要离开吗!';

  e.returnValue = message;

  return message

});

 

unload

unload事件在窗口关闭或者document对象将要卸载时触发,发生在window、body、frameset等对象上面。它的触发顺序排在beforeunload、pagehide事件后面。unload事件只在页面没有被浏览器缓存时才会触发,换言之,如果通过按下“前进/后退”导致页面卸载,并不会触发unload事件。

 

load、error

load事件直到文档和所有图片加载完毕时才会触发,error事件在页面加载失败时触发。注意,页面从浏览器缓存加载,并不会触发load事件。

 

pageshow、pagehide

默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。

 

pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数。

pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时,这个属性是false;当页面从缓存加载时,这个属性是true。

 

pagehide事件与pageshow事件类似,当用户通过“前进/后退”按钮,离开当前页面时触发。

pagehide事件的event对象有一个persisted属性,将这个属性设为true,就表示页面要保存在缓存中;设为false,表示网页不保存在缓存中,这时如果设置了unload事件的监听函数,该函数将在pagehide事件后立即运行。

 

(2)DOMContentLoaded事件、readystatechange事件

DOMContentLoaded事件:当文档加载解析完毕且所有延迟(deferred)脚本(图片未加载完毕)都执行完毕时会触发,此时图片和异步(async)脚本可能依旧在加载,但是文档已经为操作准备就绪了。也就是说,这个事件,发生在load事件之前。

document.addEventListener('DOMContentLoaded',handler,false);

 

readystatechange事件:document.readyState属性会随着文档加载过程而变,而每次状态改变,Document对象上的readystatechange事件都会触发。

document.onreadystatechange = function() {

  if(document.readyState == 'complete'){

    

  }

}

 

(3)scroll事件、resize事件

scroll事件在文档或文档元素滚动时触发,主要出现在用户拖动滚动条。

resize事件在改变浏览器窗口大小时触发,发生在window、body、frameset对象上面。

 

(4)hashchange事件、popstate事件

hashchange事件在URL的hash部分(即#号后面的部分,包括#号)发生变化时触发。如果老式浏览器不支持该属性,可以通过定期检查location.hash属性,模拟该事件。

 

popstate事件在浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()或history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()、history.forward()、history.go()时触发。

 

(5)cut事件、copy事件、paste事件

这三个事件属于文本操作触发的事件。

cut事件:在将选中的内容从文档中移除,加入剪贴板后触发。   

copy事件:在选中的内容加入剪贴板后触发。   

paste事件:在剪贴板内容被粘贴到文档后触发。

这三个事件都有一个clipboardData只读属性。该属性存放剪贴的数据,是一个DataTransfer对象。

 

(6)焦点事件

焦点事件发生在Element节点和document对象上。

focus事件:Element节点获得焦点后触发,该事件不会冒泡。   

blur事件:Element节点失去焦点后触发,该事件不会冒泡。   

focusin事件:Element节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡。Firefox不支持该事件。   

focusout事件:Element节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡。Firefox不支持该事件。

这四个事件的事件对象,带有target属性(返回事件的目标节点)和relatedTarget属性(返回一个Element节点)。对于focusin事件,relatedTarget属性表示失去焦点的节点;对于focusout事件,表示将要接受焦点的节点;对于focus和blur事件,该属性返回null。 

 

由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。

 

10.4 鼠标事件

(1)click

click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。

 

“鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。

 

(2)contextmenu

contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。

 

可以通过下面的方式阻止“上下文菜单”的出现:

document.οncοntextmenu=function(){        

  return false;    

}

 

(3)dblclick

dblclick事件当用户在element、document、window对象上,双击鼠标时触发。该事件会在mousedown、mouseup、click之后触发。

 

(4)mousedown、mouseup

mouseup事件在释放按下的鼠标键时触发。 

mousedown事件在按下鼠标键时触发。

 

(5)mousemove

mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。

 

(6)mouseover、mouseenter

mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。 

 

两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。

 

(7)mouseout、mouseleave

mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。

 

除了“mouseenter”和“mouseleave”外的所有鼠标事件都能冒泡。链接和提交按钮上的click事件都有默认操作且能够阻止。可以取消上下文菜单事件来阻止显示上下文菜单。

 

传递给鼠标事件处理程序的事件对象有clientX和clientY属性,它们指定了鼠标指针相对于包含窗口的坐标。加入窗口的滚动偏移量可以把鼠标位置转换成文档坐标。

 

MouseEvent对象的属性

 

(1)button、buttons

button属性指定当事件发生时哪个鼠标按键按下。

-1:没有按下键。 

0:按下主键(通常是左键)。 

1:按下辅助键(通常是中键或者滚轮键)。 

2:按下次键(通常是右键)。

buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。

1:二进制为001,表示按下左键。  

2:二进制为010,表示按下右键。  

4:二进制为100,表示按下中键或滚轮键。

同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。

 

(2)clientX,clientY 

clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。 

clientY属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。

 

(3)movementX,movementY 

movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。 

movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。

 

(4)screenX,screenY 

screenX属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。 

screenY属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。

 

(7)pageX、pageY

 pageX和pageY分别是触点相对HTML文档左边沿的X坐标和触点相对HTML文档上边沿的Y坐标。只读属性。

 当存在滚动的偏移时,pageX包含了水平滚动的偏移,pageY包含了垂直滚动的偏移。

 

(6)relatedTarget

relatedTarget属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。

 

10.5 鼠标滚轮事件

所有的现代浏览器都支持鼠标滚轮,并在用户滚动滚轮时触发事件。浏览器通常使用鼠标滚轮滚动或缩放文档,但可以通过取消mousewheel事件来阻止这些默认操作。

 

所有浏览器都支持“mousewheel”事件,但Firefox使用“DOMMouseScroll”事件。

 

传递给“mousewheel”处理程序的事件对象有wheelDelta属性,其指定用户滚动滚轮有多远(根据这个判断滚动方向)。

 

远离用户方向的一次鼠标滚轮“单击”的wheelDelta值通常是120,而接近用户方向的一次“单击”的值是-120。返回的总是120的倍数(120表明mouse向上滚动,-120表明鼠标向下滚动)

 

在Safari和Chrome中,为了支持使用二维轨迹球而非一维滚轮的Apple鼠标,除了wheelDelta属性外,事件对象还有wheelDeltaX和wheelDeltaY,而wheelDelta和wheelDeltaY的值一直相同。

 

而在Firefox中,传递给“DOMMouseScroll”的属性是detail。不过, detail属性值的缩放比率和正负符号不同wheelDelta,detail值乘以-40和wheelDelta值相等。记录其滚动距离的是“detail”属性,它返回的是3的倍数(3表明mouse向下滚动,-3表明mouse向上滚动)。

window.onmousewheel = document.onmousewheel = scrollWheel;

 

function scrollWheel(e){

  e = e || window.event;   

  if(e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件                

    if(e.wheelDelta > 0) {   

      //当滑轮向上滚动时       

    } else if(e.wheelDelta < 0) { 

      //当滑轮向下滚动时        

    };   

  } else if(e.detail) { //Firefox滑轮事件   

    if(e.detail < 0) { 

      //当滑轮向上滚动时      

    } else if(e.detail > 0) { 

      //当滑轮向下滚动时          

    };   

  };

}

 

10.6 键盘事件

键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。  

keydown:按下键盘时触发该事件。  

keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件。 

keyup:松开键盘时触发该事件。

textinput

任何时候,只要用户输入文本都会触发。在Webkit浏览器中支持“textInput”事件。

 

事件对象属性data(保存输入文本),inputMethod属性(用于指定输入源)

 

注意:keypress和textinput事件是在新输入的文本真正插入到聚焦的文档元素前触发的。

 

如果用户一直按键不松开,就会重复触发keydown、keypress,直到用户松开才会触发keyup。

 

属性

keycode

指定了输入字符的编码。在Firefox中使用的是charCode属性。

 

altKey,ctrlKey,metaKey,shiftKey

altKey、ctrlKey、metaKey和shiftKey属性指定了当事件发生时是否有各种键盘辅助键按下。

altKey属性:alt键  

ctrlKey属性:key键  

metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键)  

shiftKey属性:Shift键

 

key,charCode key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。 

 

主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。 

 

charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。

 

String.fromCharCode()

一个keypress事件表示输入的单个字符。事件对象以数字Unicode编码的形式指定字符,所以必须用String.fromChatCode()把它转换成字符串。

 

10.7 表单事件

(1)input、propertychange

检测文本输入元素的value属性改变,这两个事件是在新输入的文本真正插入到聚焦的文档元素前触发的。

 

一般用在