以前用过一段时间的jquery感觉太方便,太强大了,各种动画效果,dom事件、创建节点、遍历、控件及UI库,应有尽有;开发文档也很多,网上讨论的问题更是甚多,种种迹象表明jquery是一个出色的javascript框架库,必须顶一个。但我已经多年不用jquery了,这个跟工作性质有关系。
作为开发的我们,不应该仅仅局限于使用某一个功能,应该更多的去搞懂它的实现原理及机制,才能谈设计、谈创新,以下就搜集整理了一些javascript事件机制相关的信息。
- 直接将JS代码写在HTML上
测试:cnblogs
1 |
<div onclick= "alert('欢迎访问cnblogs');" >cnblogs</div> |
HTML Element元素自身就拥有了很多onXXX属性,只需将JS代码赋值给其就可以了。赋值给onXXX的字符串将作为响应函数的函数体(FunctionBody)。大概这是上世纪90年代的写法,那时候直接把JS代码写在网页中很普遍,也许那时候的JS并不太重要,只是用来做做验证或一些花哨的效果而已。
2. 定义一个函数,赋值给html元素的onXXX属性
1 |
<script type= "text/javascript" > |
4 |
<div onclick= "clk()" >Div2 Element</div> |
先定义函数clk,然后赋值给onclick属性,这种方式也应该属于上世纪90年代的流行写法。比第一种方式好的是它把业务逻辑代码都封装在一个函数里了,使HTML代码与JS代码稍微有点儿分离,不至于第一种那么紧密耦合。
- 使用element.onXXX方式
1 |
<div id= "d3" >Div3 Element</div> |
2 |
<script type= "text/javascript" > |
3 |
var d3 = document.getElementById( 'd3' ); |
4 |
d3.onclick = function (){ } |
这种方式也是早期的写法,但好处是可以将JS与HTML完全分离,前提是需要给HTML元素提供一个额外的id属性(或其它能获取该元素对象的方式)。
- 使用addEventListener或attachEvent
01 |
<div id= "d4" >Div4 Element</div> |
02 |
<script type= "text/javascript" > |
03 |
var d4 = document.getElementById( 'd4' ); |
04 |
function clk(){alert(4)} |
05 |
if (d4.addEventListener){ |
06 |
d4.addEventListener( 'click' ,clk, false ); |
09 |
d4.attachEvent( 'onclick' ,clk); |
这是目前推荐的方式,较前两种方式功能更为强大,可以为元素添加多个事件handler,支持事件冒泡或捕获,前三种方式默认都是冒泡。IE6/7/8仍然没有遵循标准而使用了自己专有的attachEvent,且不支持事件捕获。
好,把方式4简单的封装下, 兼容标准浏览器及IE浏览器。注意attachEvent的第一个参数需要加上个"on",addEventListener第三个参数为false表示事件冒泡,attachEvent没有第三个参数,默认就是冒泡,没有捕获。
03 |
* @param {Object} el HTML Element |
04 |
* @param {Object} type 事件类型 |
05 |
* @param {Object} fn 事件handler |
07 |
function addEvent(el, type, fn){ |
08 |
if (el.addEventListener){ |
09 |
el.addEventListener(type, fn, false ); |
11 |
el.attachEvent( 'on' + type, fn); |
好,用这个工具函数添加一个给document添加一个点击事件:
5 |
addEvent(document, 'click' , handler); |
在Firefox等标准浏览器中,点击页面后将弹出 "[object HTMLDocument]",及handler中的this就是document自身。但在IE6/7/8中this却是window对象。这让人不爽,修改下与标准浏览器统一。
01 |
function addEvent(el, type, fn){ |
02 |
if (el.addEventListener){ |
03 |
el.addEventListener(type, fn, false ); |
05 |
el[ 'e' + fn] = function (){ |
06 |
fn.call(el, window.event); |
08 |
el.attachEvent( 'on' +type, el[ 'e' +fn]); |
上面我们封装了一个addEvent,解决了IE6/7/8下事件handler中this为window的错误,并且统一了事件对象作为事件handler的第一个参数传入。
这篇把对应的删除事件的函数补上。上一篇中fn在IE6/7/8中实际上被包装了,IE6/7/8中真正的handler是el["e"+fn]。因此删除时要用到它。同时将两个方法挂在一个对象E上,add,remove分别添加和删除事件。
03 |
add : function (el, type, fn){ |
04 |
if (el.addEventListener){ |
05 |
el.addEventListener(type, fn, false ); |
07 |
el[ 'e' +fn] = function (){ |
10 |
el.attachEvent( 'on' + type, el[ 'e' +fn]); |
14 |
remove : function (el, type, fn){ |
15 |
if (el.removeEventListener){ |
16 |
el.removeEventListener(type, fn, false ); |
17 |
} else if (el.detachEvent){ |
18 |
el.detachEvent( 'on' + type, el[ 'e' +fn]); |
可以看到,标准浏览器如IE9/Firefox/Safari/Chrome/Opera会使用addEventListener/removeEventListener添加/删除事件,IE6/7/8则使用attachEvent/detachEvent。标准浏览器中事件handler是传入的第三个参数fn,IE6/7/8中则是包装后的el["e"+fn]。
好了,已经拥有了添加,删除事件两个方法,并且解决了各浏览器下中的部分差异,现再添加一个主动触发事件的方法dispatch。该方法能模拟用户行为,如点击(click)操作等。 标准使用dispatchEvent方法,IE6/7/8则使用fireEvent方法。因为可能会出现异常,使用了try catch。
03 |
add : function (el, type, fn){ |
04 |
if (el.addEventListener){ |
05 |
el.addEventListener(type, fn, false ); |
07 |
el[ 'e' +fn] = function (){ |
08 |
fn.call(el,window.event); |
10 |
el.attachEvent( 'on' + type, el[ 'e' +fn]); |
14 |
remove : function (el, type, fn){ |
15 |
if (el.removeEventListener){ |
16 |
el.removeEventListener(type, fn, false ); |
17 |
} else if (el.detachEvent){ |
18 |
el.detachEvent( 'on' + type, el[ 'e' +fn]); |
22 |
dispatch : function (el ,type){ |
25 |
var evt = document.createEvent( 'Event' ); |
26 |
evt.initEvent(type, true , true ); |
27 |
el.dispatchEvent(evt); |
28 |
} else if (el.fireEvent){ |
29 |
el.fireEvent( 'on' +type); |
这就是整个事件模块的雏形,往后还有很多需要补充完善的地方。但对于普通的应用,这几个函数足以胜任。
上面的add有个问题,对同一类型事件添加多个hanlder时,IE6/7/8下会无序,如
01 |
<div id= "d1" style= "width:200px;height:200px;background:gold;" ></div> |
02 |
<script type= "text/javascript" > |
03 |
var el = document.getElementById( 'd1' ); |
04 |
function handler1(){alert( '1' );} |
05 |
function handler2(){alert( '2' );} |
06 |
function handler3(){alert( '3' );} |
07 |
function handler4(){alert( '4' );} |
08 |
function handler5(){alert( '5' );} |
09 |
E.add(el, 'click' , handler1); |
10 |
E.add(el, 'click' , handler2); |
11 |
E.add(el, 'click' , handler3); |
12 |
E.add(el, 'click' , handler4); |
13 |
E.add(el, 'click' , handler5); |
IE9/Firefox/Safari/Chomre/Opera会依次输出1,2,3,4,5。但IE6/7/8中则不一定。为解决所有浏览器中多个事件handler有序执行,我们需要一个队列来管理所有的handler。
这次,把所有的内部细节封装在一个匿名函数中,该函数执行完毕后返回如上一篇接口相同的方法。另外
- 把真正的事件handler挂在el上,即el.listeners,其为一个对象,每一个类型的事件为一个数组,如click为el.listeners["click"] = []。
- 所有的handler存在在对于的数组中
- 删除一个hanlder,将从数组中将其删除
02 |
function _isEmptyObj(obj){ |
08 |
function _each(ary, callback){ |
09 |
for ( var i=0,len=ary.length; i<len;){ |
10 |
callback(i, ary[i]) ? i=0 : i++; |
13 |
function _remove(el, type){ |
14 |
var handler = el.listeners[type][ '_handler_' ]; |
15 |
el.removeEventListener ? |
16 |
el.removeEventListener(type, handler, false ) : |
17 |
el.detachEvent( 'on' +type, handler); |
18 |
delete el.listeners[type]; |
19 |
if (_isEmptyObj(el.listeners)){ |
24 |
function add(el, type, fn){ |
25 |
el.listeners = el.listeners || {}; |
26 |
var listeners = el.listeners[type] = el.listeners[type] || []; |
28 |
if (!listeners[ '_handler_' ]){ |
29 |
listeners[ '_handler_' ] = function (e){ |
30 |
var evt = e || window.event; |
31 |
for ( var i=0,fn; fn=listeners[i++];){ |
36 |
el.addEventListener(type, listeners[ '_handler_' ], false ) : |
37 |
el.attachEvent( 'on' + type, listeners[ '_handler_' ]); |
41 |
function remove(el, type, fn){ |
42 |
if (!el.listeners) return ; |
43 |
var listeners = el.listeners && el.listeners[type]; |
45 |
_each(listeners, function (i, f){ |
47 |
return listeners.splice(i, 1); |
50 |
if (listeners.length == 0){ |
56 |
function dispatch(el ,type){ |
59 |
var evt = document.createEvent( 'Event' ); |
60 |
evt.initEvent(type, true , true ); |
61 |
el.dispatchEvent(evt); |
62 |
} else if (el.fireEvent){ |
63 |
el.fireEvent( 'on' +type); |
上面解决了IE6/7/8中同一个类型事件的多个handler执行无序的情况,为此改动也是较大的。实现几乎与前一个版本完全不同。但好处也是明显的。
有时需要添加只执行一次的事件handler,为此给add方法添加第四个参数one,one为true则该事件handler只执行一次。
1 |
<div id= "d1" style= "width:200px;height:200px;background:gold;" ></div> |
3 |
var el = document.getElementById( 'd1' ); |
4 |
function handler(){alert(5)} |
5 |
E.add(el, 'click' , handler, true ); |
再扩展下remove函数。
- 删除元素type类型的所有监听器(参数传el,type)
- 删除元素所有的监听器(仅传el)
比如当给一个el添加了3个click事件的handler,1个mouseover事件的handler
1 |
function handler1(){alert( '1' );} |
2 |
function handler2(){alert( '2' );} |
3 |
function handler3(){alert( '3' );} |
4 |
function handler4(){alert( '4' );} |
5 |
E.add(el, 'click' , f1); |
6 |
E.add(el, 'click' , f2); |
7 |
E.add(el, 'click' , f3); |
8 |
E.add(el, 'mouseover' , f4); |
使用以下语句将删除元素click的所有handler:E.remove(el, 'click');
以下将删除元素身上所有的事件handler,包括click和mouseover:E.remove(el);
上面正式推出了我的事件模块event_v1,已经搭起了它的初始框架。或许有人要说,与众多JS库或框架相比,它还没有解决事件对象的兼容性问题。是的,我故意将此放到后续补充。因为事件对象的兼容性问题太多了,太繁琐了。
下面我将引入一个私有的_fixEvent函数,add中将调用该函数。_fixEvent将修复(或称包装)原生事件对象,返回一个标准的统一接口的事件对象。如下
01 |
function _fixEvent( evt, el ) { |
02 |
var props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which" .split( " " ), |
04 |
function now() { return ( new Date).getTime();} |
05 |
function returnFalse() { return false ;} |
06 |
function returnTrue() { return true ;} |
07 |
function Event( src ) { |
08 |
this .originalEvent = src; |
10 |
this .timeStamp = now(); |
13 |
preventDefault: function () { |
14 |
this .isDefaultPrevented = returnTrue; |
15 |
var e = this .originalEvent; |
16 |
if ( e.preventDefault ) { |
19 |
e.returnValue = false ; |
21 |
stopPropagation: function () { |
22 |
this .isPropagationStopped = returnTrue; |
23 |
var e = this .originalEvent; |
24 |
if ( e.stopPropagation ) { |
27 |
e.cancelBubble = true ; |
29 |
stopImmediatePropagation: function () { |
30 |
this .isImmediatePropagationStopped = returnTrue; |
31 |
this .stopPropagation(); |
33 |
isDefaultPrevented: returnFalse, |
34 |
isPropagationStopped: returnFalse, |
35 |
isImmediatePropagationStopped: returnFalse |
38 |
var originalEvent = evt; |
39 |
evt = new Event( originalEvent ); |
41 |
for ( var i = len, prop; i;) { |
43 |
evt[ prop ] = originalEvent[ prop ]; |
46 |
evt.target = evt.srcElement || document; |
48 |
if ( evt.target.nodeType === 3 ) { |
49 |
evt.target = evt.target.parentNode; |
51 |
if ( !evt.relatedTarget && evt.fromElement ) { |
52 |
evt.relatedTarget = evt.fromElement === evt.target ? evt.toElement : evt.fromElement; |
54 |
if ( evt.pageX == null && evt.clientX != null ) { |
55 |
var doc = document.documentElement, body = document.body; |
56 |
evt.pageX = evt.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); |
57 |
evt.pageY = evt.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); |
59 |
if ( !evt.which && ((evt.charCode || evt.charCode === 0) ? evt.charCode : evt.keyCode) ) { |
60 |
evt.which = evt.charCode || evt.keyCode; |
62 |
if ( !evt.metaKey && evt.ctrlKey ) { |
63 |
evt.metaKey = evt.ctrlKey; |
65 |
if ( !evt.which && evt.button !== undefined ) { |
66 |
evt.which = (evt.button & 1 ? 1 : ( evt.button & 2 ? 3 : ( evt.button & 4 ? 2 : 0 ) )); |
68 |
if (!evt.currentTarget) evt.currentTarget = el; |
好了,现在你要
- 阻止事件默认行为,统一使用e.preventDefault()
- 停止冒泡,统一使用e. stopPropagation()
- 获取事件源,统一使用e.target
更多的差异性,不在这一一列举了。原文链接:http://www.nowamagic.net/javascript/js_EventMechanismInDetail.php
最后举一个事件冒泡的例子:
javascript的事件模型,采用”冒泡”模式,也就是说,子元素的事件会逐级向上”冒泡”,成为父元素的事件。
利用这一点,可以大大简化事件的绑定。比如,有一个表格(table元素),里面有100个格子(td元素),现在要求在每个格子上面绑定一个点击事件(click),请问是否需要将下面的命令执行100次?
$("td").on("click", function(){
$(this).toggleClass("click");
});
回答是不需要的,我们只要把这个事件绑定在table元素上面就可以了,因为td元素发生点击事件之后,这个事件会”冒泡”到父元素table上面,从而被监听到。
因此,这个事件只需要在父元素绑定1次即可,而不需要在子元素上绑定100次,从而大大提高性能。这就叫事件的”委托处理”,也就是子元素”委托”父元素处理这个事件。
最后通过事件参数e,取得当前的事件元素e.target,通过它可以做一些判断,完成点击事件的业务处理。
$("table").on("click", "td", function(){ $(this).toggleClass("click"); });
- 【移动端兼容问题研究】javascript事件机制详解(涉及移动兼容)
前言 这篇博客有点长,如果你是高手请您读一读,能对其中的一些误点提出来,以免我误人子弟,并且帮助我提高 如果你是javascript菜鸟,建议您好好读一读,真的理解下来会有不一样的收获 在下才疏学浅, ...
- Javascript事件机制兼容性解决方案
本文的解决方案可以用于Javascript native对象和宿主对象(dom元素),通过以下的方式来绑定和触发事件: 或者 var input = document.getElementsByTag ...
- 【探讨】javascript事件机制底层实现原理
前言 又到了扯淡时间了,我最近在思考javascript事件机制底层的实现,但是暂时没有勇气去看chrome源码,所以今天我来猜测一把 我们今天来猜一猜,探讨探讨,javascript底层事件机制是如 ...
- JavaScript事件机制——细思极恐
JavaScript事件机制,也有让人深思的东西.在一开始未深入了解,我头脑里有几个问题发出: 1. 自下而上(冒泡)事件怎么写,自上而下(捕获)又是怎么写? 2. 捕获型和冒泡型同时设置,谁生效? ...
- 【JavaScript】重温Javascript继承机制
上段时间,团队内部有过好几次给力的分享,这里对西风师傅分享的继承机制稍作整理一下,适当加了些口语化的描述,留作备案. 一.讲个故事吧 澄清在先,Java和Javascript是雷锋和雷峰塔的关系.Ja ...
- [解惑]JavaScript事件机制
群里童鞋问到关于事件传播的一个问题:“事件捕获的时候,阻止冒泡,事件到达目标之后,还会冒泡吗?”. 初学 JS 的童鞋经常会有诸多疑问,我在很多 QQ 群也混了好几年了,耳濡目染也也收获了不少,以后会 ...
- 总结JavaScript事件机制
JavaScript事件模型 在各种浏览器中存在三种事件模型: 原始事件模型 , DOM2事件模型 , IE事件模型. 其中原始的事件模型被所有浏览器所支持,而DOM2中所定义的事件模型目前被除了IE ...
- javascript事件机制
① javascript绑定事件的方式 http://blog.iderzheng.com/dom-javascript-event-binding-comparison/ ② javascript事 ...
- 对JavaScript事件机制的一点理解
JavaScript通过事件机制实现了异步操作,这种异步操作可以使CPU可以在IO任务的等待中被释放出来处理其他任务,等待IO结束再去处理这个任务.这个是一个基本的事件机制. 那么是不是说事件从监听到 ...
随机推荐
- Objective-C 谈谈深浅拷贝,copy和mutable copy都不是完全拷贝
(一)字符串中的指针赋值,copy和mutablecopy NSString和NSString (1)指针赋值 肯定指向同一个字符串地址. (2)copy(和直接指向一样) NSString *str ...
- HDU 1231 最大连续子序列(水题)
题目链接: 传送门 最大连续子序列 Time Limit: 1000MS Memory Limit: 32768 K Description 给定K个整数的序列{ N1, N2, ..., N ...
- UVA3026Period(最短循环节)
题目链接 题意: 给定长度为n的字符串s,求他的每个前缀的最短循环节 分析: kmp预处理 next[]数组,然后对于 前 i 个字符,如果 next[i] > 0 && i % ...
- php删除指定目录所有文件
<?php /** * 删除指定文件目录下的所有文件 * @param str $dir 指定文件路径: 如:K:/wamp/www/test * return boole *--------- ...
- JS-鼠标经过显示二级菜单
在css处添加了border样式为了看得更清楚——源代码有一个程序漏洞,存在一个很烦人的大bug. <ul class="nav"> <li class=&quo ...
- 淘淘商城maven工程的创建和svn的上传实现
后台管理系统工程结构 maven管理的好处 1.项目构建.Maven定义了软件开发的整套流程体系,并进行了封装,开发人员只需要指定项目的构建流程,无需针对每个流程编写自己的构建脚本. 2.依赖管理.除 ...
- DataReader和DataSet的异同
DataReader:使用时始终占用SqlConnection,在线操作数据库:每次只在内存中加载一条数据,所以占用的内存是很小的:是只进的. 只读的. DataSet:则是将数据一次性加载在内存中. ...
- ----------jqery和js如何判断checkbox是否选中 --------两个单选按钮如何选一个,且用jquery获取被选的值
jqery和js如何判断checkbox是否选中 jquery: <div id="divId" class="divTable"> <div ...
- Axure7.0汉化方法
下载汉化包 AxureRP7CN_汉化包.rar 首先退出正在运行中的 Axure (如果您正在使用). 将 汉化包.rar 文件解压, 得到 lang 文件夹, 然后将其复制到 Axure 安装目 ...
- MyISAM 和InnoDB 区别 转
MyISAM 和InnoDB 讲解 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级处理 ...