JS事件简单总结

来源:互联网 发布:windows凭据无法保存 编辑:程序博客网 时间:2024/06/06 07:16

对JS事件模型不算不熟悉,只是曾经面试的时候,人家问我阻止冒泡,阻止默认事件的函数是什么,那个preventDefault,stopPropagation在我口中愣是没说出来。心都碎了。。。现在整理一下,加深自己的印象,当成完善自己的知识体系。

正文从这里开始。

什么是事件

这个其实不是很好描述,有可能是由用户发起的,比如鼠标事件,键盘事件,窗口事件,表单事件,也有可能是页面加载资源过程中的事件。常见的事件比如:click, dbclick, keydown, keypress, keyup, mousemove, wheel, scroll, focus, blur, load, unload, abort, error, resize, change, select, submit, 大概这些及其相关的。

事件传播机制

标准的事件是怎么触发以及传播的呢?一图胜千言。

总体来说,事件传播分为三个阶段:

1. 事件捕获阶段(capture phase),就是事件从window开始一层一层向目标传递的阶段2. 目标阶段(target phase),当事件到达事件发生现场的阶段3. 事件冒泡阶段(bubble phase),和事件捕获阶段相反,事件从目标到window传递的阶段

P.S. IE8及其以下的事件只有冒泡,没有捕获。

事件委托/代理

有个提到比较多的就是事件委托。因为事件有冒泡机制,我们可以不在目标元素监听事件,而在它的父元素监听。一个比较经典的例子,一个拥有很多子项的无序列表,需要监听子项上的点击事件。此时,我们将监听绑定在父元素ul上,当点击li元素时,由于事件的冒泡机制,ul层也能触发点击事件。但是如何判断你点击的就是你想要的li元素,而不是ul元素本身,用事件对象event的目标元素属性target来判断就好了,即event.target。

这样做的优点有:提高性能,从监听多个事件减少到监听一个事件,效率肯定得到提升;能动态地自适应,比如再在ul元素内加一个li子元素,传统的方法就需要再增加一个监听事件,而利用事件委托就可以以不变应万变。

事件实现方式

三种实现方式:

原始事件实现方式(DOM0)

也称DOM0事件处理方式,DOM0不是W3C的标准,由于历史发展的原因,它存在过也一直存在着。很简单,只要在html元素中添加on+事件的属性即可。

<button id='myButton' onclick='doSomething()'> 按钮 </button>

或者用js的方式给元素添加这个属性。

var btEle = document.getElementById('myButton');btEle.onclick = doSomething;

这样的优点是简单方便,并且兼容所有的浏览器;缺点也不少,违反了行为与表现分离的准则,只能添加一个事件,也不能利用事件委托机制去更多的事。

IE事件实现方式

上文提到IE8及其以下的事件传播机制只有冒泡,没有捕获,实现方式为

//监听事件element.attachEvent('on' + eventType, callback);//解除监听element.detachEvent('on' + eventType, callback);//手动触发事件,兼容性IE6-10element.fireEvent('on' + eventType)

需要注意的是,匿名函数无法被解除监听。

标准事件实现方式(DOM2)

DOM2规定的标准应该大一统了浏览器,而之后变得不多。目前W3C最新的标准是15年出的DOM4。我们来简单看下。

//监听事件element.addEventListener(eventType, callback, useCapture);//解除监听element.removeEventListener(eventType, callback, useCapture);//手动触发事件element.dispatchEvent(eventType)//回调函数中有个event对象记录事件的属性 function callback(event){    //do something}

因为标准的事件传播机制有捕获和冒泡阶段,这里最后一个参数useCapture表示是否在事件捕获阶段就执行回调函数,默认为false。一般情况下,我们使用默认值,即在事件冒泡阶段执行函数。为什么?主要是为了兼容旧版的IE。

事件常见属性

我们已经知道,事件的回调函数中有个事件对象的参数。从第一部分,我们已经看到Event对象的继承关系,比如我们最常见的鼠标事件或者键盘事件,MouseEvent继承自UIEvent,UIEvent继承自Event。我们参考最新的W3C规范,来具体看下。

[Constructor(DOMString type, optional EventInit eventInitDict),Exposed=(Window,Worker)]interface Event {  // 事件的类型,比如click,submit,load等  readonly attribute DOMString type;  // 触发事件的那个目标元素  readonly attribute EventTarget? target;  // 事件传播所在的当前元素  readonly attribute EventTarget? currentTarget;  const unsigned short NONE = 0;  const unsigned short CAPTURING_PHASE = 1;  const unsigned short AT_TARGET = 2;  const unsigned short BUBBLING_PHASE = 3;  // 事件所在的阶段,枚举值如上  readonly attribute unsigned short eventPhase;  // 阻止事件的冒泡,在当前元素的冒泡执行完之后  void stopPropagation();  // 立即阻止冒泡,包括当前元素还有其他的回调事件  // DOM3增加新属性   void stopImmediatePropagation();  // 是否在冒泡阶段  readonly attribute boolean bubbles;  // 是否可以被取消,用preventDefault取消  readonly attribute boolean cancelable;  // 取消当前元素的默认事件,前提是cancelable为true  void preventDefault();  // 是否被preventDefault过,DOM3增加新属性   readonly attribute boolean defaultPrevented;  // 事件是否有用户触发的  [Unforgeable] readonly attribute boolean isTrusted;  // 当前时间戳  readonly attribute DOMTimeStamp timeStamp;  void initEvent(DOMString type, boolean bubbles, boolean cancelable);};dictionary EventInit {  boolean bubbles = false;  boolean cancelable = false;};
[Constructor(DOMString type, optional MouseEventInit eventInitDict)]interface MouseEvent : UIEvent {  // 相对屏幕的x,y坐标  readonly attribute long screenX;   readonly attribute long screenY;  // 相对浏览器可视区域的x,y坐标  readonly attribute long clientX;  readonly attribute long clientY;  //是否按下这些特殊的键  readonly attribute boolean ctrlKey;  readonly attribute boolean shiftKey;  readonly attribute boolean altKey;  readonly attribute boolean metaKey;  //哪个鼠标键被按下,0主键,1滚轮,2附键,只在mouseup,mousedown事件中有效  readonly attribute short button;  // 被激活的鼠标,可以是多个,0没有,1主键,2附键,4滚轮,8后退?,16前进?  // 还可以相加,比如按下了主键和滚轮,就是1+4 = 5  readonly attribute unsigned short buttons;   readonly attribute EventTarget? relatedTarget;   boolean getModifierState(DOMString keyArg);};

以上是规范,但实际上浏览器的实现上好像还有很多杂七杂八的属性。以下是在Chrome 56输出的结果。

    1. MouseEvent
      1. altKey:false
      2. bubbles:true
      3. button:0
      4. buttons:0
      5. cancelBubble:false
      6. cancelable:true
      7. clientX:97
      8. clientY:23
      9. composed:true
      10. ctrlKey:false
      11. currentTarget:null
      12. defaultPrevented:false
      13. detail:1
      14. eventPhase:0
      15. fromElement:null
      16. isTrusted:true
      17. layerX:97
      18. layerY:23
      19. metaKey:false
      20. movementX:0
      21. movementY:0
      22. offsetX:28
      23. offsetY:13
      24. pageX:97
      25. pageY:23
      26. path:Array[5]
      27. relatedTarget:null
      28. returnValue:true
      29. screenX:144
      30. screenY:181
      31. shiftKey:false
      32. sourceCapabilities:InputDeviceCapabilities
      33. srcElement:button
      34. target:button
      35. timeStamp:2414.235
      36. toElement:button
      37. type:”click”
      38. view:Window
      39. which:1
      40. x:97
      41. y:23
      42. proto:MouseEvent

感觉眼睛都要花了,比较常用的感觉不多,也看场景吧。一般比如target,preventDefalut,stopPropagation,type,以及坐标的属性比较常用吧。

由于浏览器的历史原因,关于鼠标坐标的属性真是好多对:

属性 描述 x, y 在整个浏览器窗口所处的位置 screenX, screenY 在整个屏幕中所处于位置 clientX, clientY 在整个浏览器窗口所处的位置,与x, y属性相同 layerX, layerY 在整个元素的区域鼠标的位置(但算上了scroll的距离),当不随页面滚动而变化 pageX, pageY 在整个页面所处的位置,当不随页面滚动而变化 offsetX, offsetY 在整个元素的区域,鼠标的位置,当不随页面滚动而变化 movementX, movementY 两个事件之间鼠标的位移差

最好使用W3C规范里面的两对属性,是最安全和最保险的选择,screenX, screenY, clientX, clientY。

简单的兼容实现

主要是考虑对IE8及其以下的兼容性。

function addEventListener(target, event, callback)    if( window.addEventListener ){        target.addEventListener(event, callback)l    }    else if(window.attachEvent){        target.attachEvent('on' + event, callback);    }    else{        target['on' + event] = callback;    }}

事件的执行顺序

我们用一个简单的例子在Chrome 56中做测试。

// HTML<div id="parent" onclick="parentCB(window.event)">    <div id="child" onclick="childCB(window.event)"></div></div>//SCRIPTvar phases = {  1: 'capture',  2: 'target',  3: 'bubble'};function parentCB(e){    console.log('parent dom0 html ' + phases[e.eventPhase] );}function childCB(e){    console.log('child dom0 html ' + phases[e.eventPhase] );}var parent = document.getElementById('parent');parent.addEventListener('click', function(e){    console.log('parent capture ' + phases[e.eventPhase]);}, true)parent.onclick = function(e){    console.log('parent dom0 js ' + phases[e.eventPhase]);}parent.addEventListener('click', function(e){    console.log('parent bubble ' + phases[e.eventPhase]);})var child = document.getElementById('child');child.addEventListener('click', function(e){    console.log('child bubble ' + phases[e.eventPhase]);})child.addEventListener('click', function(e){    console.log('child capture ' + phases[e.eventPhase]);}, true)// child.onclick = function(e){//     console.log('child dom0 js ' + phases[e.eventPhase]);// }

当点击parent div的时候输出如下:

parent capture targetparent dom0 js targetparent bubble target

而点击child div的时候输出如下:

parent capture capturechild dom0 html targetchild bubble targetchild capture targetparent dom0 js bubbleparent bubble bubble

当你不断地调整那些事件的顺序,或者引入js的onclick函数,你会发现以下规律:

1. 当html中有onclick事件,js中也有onclick事件的时候,js中onclick事件会覆盖html中的事件。因为其实它们是同一个属性。2. 目标元素的事件执行顺序跟定义的顺序一致,而不会按照先捕获后冒泡的顺序。在html中定义的事件排在第一个。3. 非目标事件的函数执行遵守先捕获后冒泡的规律,并且DOM0级元素定义的事件会在冒泡阶段先执行,不管是在html定义还是用js定义。

另外,如果一个事件多次绑定,只会执行一次。

事件this指向

对于DOM0级的this情况如下。

//HTML <a href="#"  onclick ="go(this)"> hhhhhh </a><button> 按钮 </button>//SCRIPTfunction go(e){     console.log(e);  // <a href="#"  onclick ="go(this)"> hhhhhh </a>     console.log(this); //window}var bt = document.getElementsByTagName('button')[0];bt.onclick = function(e){    console.log(this); // <button> 按钮 </button>}

而attachEvent中的this指向window,addEventListener指向当前的元素。

自定义事件

在数据双向绑定的分析和简单实现中,已经简单提到了,事件的机制抽象出来就是发布订阅模式。我们可以利用自定义事件去实现我们自己想要的发布订阅模式。

// 新建事件实例var event = new Event('build');// 添加监听函数child.addEventListener('build', function (e) {    console.log(' custom event build');}, false);// 触发事件child.dispatchEvent(event);

参考资料:

  • http://javascript.ruanyifeng.com/dom/event.html 阮一峰 事件模型
  • http://yujiangshui.com/javascript-event/ 于江水 JavaScript与事件
  • https://www.w3.org/TR/DOM-Level-3-Events/#event-type-input W3C DOM3 event
  • https://www.w3.org/TR/dom/#interface-event W3C DOM4 event
  • http://www.cnblogs.com/hustskyking/p/problem-javascript-event.html 小胡子哥 【解惑】JavaScript事件机制
0 0
原创粉丝点击