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输出的结果。
- MouseEvent
- altKey:false
- bubbles:true
- button:0
- buttons:0
- cancelBubble:false
- cancelable:true
- clientX:97
- clientY:23
- composed:true
- ctrlKey:false
- currentTarget:null
- defaultPrevented:false
- detail:1
- eventPhase:0
- fromElement:null
- isTrusted:true
- layerX:97
- layerY:23
- metaKey:false
- movementX:0
- movementY:0
- offsetX:28
- offsetY:13
- pageX:97
- pageY:23
- path:Array[5]
- relatedTarget:null
- returnValue:true
- screenX:144
- screenY:181
- shiftKey:false
- sourceCapabilities:InputDeviceCapabilities
- srcElement:button
- target:button
- timeStamp:2414.235
- toElement:button
- type:”click”
- view:Window
- which:1
- x:97
- y:23
- proto:MouseEvent
- MouseEvent
感觉眼睛都要花了,比较常用的感觉不多,也看场景吧。一般比如target,preventDefalut,stopPropagation,type,以及坐标的属性比较常用吧。
由于浏览器的历史原因,关于鼠标坐标的属性真是好多对:
最好使用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事件机制
- JS事件简单总结
- js简单事件
- js事件总结
- JS事件归纳总结
- JS事件总结
- js事件总结
- JS事件总结
- js常用事件总结
- js事件总结
- js键盘事件总结
- js事件类型总结
- JS事件知识点总结
- JS----简单的事件委派
- js简单正则 总结
- js简单问题总结
- js touch事件 手势滑动事件总结
- js事件监听机制(事件捕获)总结
- js二次开发skyline事件总结
- 在ununt下安装JDK,eclipse,tomcat
- leetcode 75. Sort Colors
- 抽象工厂模式
- 书单
- 循环神经网络(RNN, Recurrent Neural Networks)介绍
- JS事件简单总结
- 多核处理器与向量处理器
- 强连通分量入门——UVA
- 读华为敏捷转型有感
- 3.4 T2(NOIP 2015)
- 使用Instruments来检测内存泄露
- 算法提高 复数四则运算
- 图像处理杂文(一)
- tensorflow