写点什么

JS 事件详解和 js 事件委托

作者:编程江湖
  • 2022 年 2 月 07 日
  • 本文字数:5435 字

    阅读完需:约 18 分钟

简述事件

事件起始于 IE3,作为一种分担服务器运算负载的一种手段。用于键盘、鼠标等工具对于网页的交互!事件对于不同浏览器来说,有不同的标准,尤其是 IE、Chrome 两大巨头浏览器上,虽然现如今 Chrome 已经占据大部分市场,但是对于 IE8 及以上的兼容也是个不小的问题。

事件类型

UI 事件:用户与页面上的元素交互时触发;

焦点事件:当元素获取失去焦点是触发;

鼠标事件:当鼠标执行点击、移入移出或悬停等事件是触发;

滚轮事件:当鼠标、触摸板或其他设备滚动时触发滚动条事件;

文本事件:在文本框内输入文字时触发;

键盘事件:当键盘按下、抬起或点击时触发;

变动事件:当问文档结构发生变化时触发。

UI 事件

load 事件:当页面完全加载后再 window 上面触发,当所有框架都加载完成时,在框架集上面触发,当图片都加载完成时在 img 标签上面触发,或者当嵌入的内容加载完成时在 object 上触发。

unload 事件:和 load 事件恰好相反,除 img 标签外,其他的框架和嵌入内容卸载完毕后在对应的级别上触发。

abort 事件:在用户停止下载过程时,如果嵌入的内容没有加载完毕,在 object 元素上线触发。

error 事件:当 js 发生语法错误时在 window 上触发,当发生加载图像无法成功时在 img 标签上触发,当嵌入的内容无法加载时在 object 元素上触发,或当一个或多个框架无法加载完成时在框架集上面触发。

select 事件:当用户选择文本框内的文字时触发(input 或 textarea)。

resize 事件:当窗口或者框架的大小变化时在 window 或框架上面触发。

scroll 事件:当用户滚动带有滚动条的元素中的内容时,在该元素上面触发。body 上就是网页自带的滚动条。

焦点事件

blur 事件、focusout

输入框失去焦点事件,css 有相同伪类选择器,但是不够灵活!

focusin 事件、focus

输入框获取焦点是触发,同样,css 有相同的伪类事件。输入框如果使用 click 同样能获取焦点,但是有点牛头马嘴的意思。

focus 和 blur 事件不支持冒泡,如果不想文档事件冒泡则使用这两个事件更好,不用屏蔽事件流。

鼠标与滚轮事件

mousedown、mouseup 事件

鼠标左键按下、抬起事件,相继触发这两个事件后还会触发 click 事件。

click 事件、dbclick 事件

鼠标单击、双击事件

mouseleave、mouseout 事件

都是鼠标脱离当前区域触发,不同的是 mouseleave 不冒泡。

mouseenter、mouseover 事件

鼠标进入、悬停在当前区域时触发,mouseenter 事件不冒泡。

mousemove 事件

当鼠标在当前区域移动时重复触发!

mousewheel 事件

滚轮事件在鼠标中间滚动时触发,同时包含一个 wheelData 属性,它是 120 的整数倍数,滚轮向上滚动一个单位时 wheelDate+120,向下滚动时-120,。

键盘与文本事件

keydown:当用户按下键盘等按键设备上的按钮时触发,按住不放会重复触发。

keyup、keypress:按键按下后释放时触发。两个事件都是同时触发 keyCode 的值,但是 keypress 会输出 input 框的上一个值,而 keyup 会立即输出当前 input 的值。如下:<body>

<input type="text" id="test">

</body>

<script>

/* 虽然 id 可以直接拿来使用,但是并不提倡,这不符合规范! */

test.οnkeypress=function(event){ //event 是系统默认传递的事件对象

console.log(event.keyCode); //输出键码

console.log(test.value); //输出 input 值

};

</script>

在我依次按下数字键 1、2、3、4、5、6 时控制台是如下效果:

index.html:15 49

index.html:16

index.html:15 50

index.html:16 1

index.html:15 51

index.html:16 12

index.html:15 52

index.html:16 123

index.html:15 53

index.html:16 1234

index.html:15 54

index.html:16 12345

但是如果是 keyup 的事件呢:

<body>

<input type="text" id="test">

</body>

<script>

/*虽然 id 可以直接拿来使用,但是并不提倡,这不符合规范!*/

test.οnkeyup=function(event){

console.log(event.keyCode);

console.log(test.value);

};

</script>

在我依次按下数字键 1、2、3、4、5、6 时控制台是如下效果:

index.html:15 49

index.html:16 1

index.html:15 50

index.html:16 12

index.html:15 51

index.html:16 123

index.html:15 52

index.html:16 1234

index.html:15 53

index.html:16 12345

index.html:15 54

index.html:16 123456

事件流

事件流描述的是事件在网页文档中的接收顺序,它描述着事件发生时,事件是顺着文档流向上还是向下!

但是奇趣的是,IE 和 Netscape 开发团队提出了两个完全相反的事件流概念!那就是事件冒泡和事件捕获!

根据网页文档的 DOM 级别规定层级结构:

文档结构 documenthtmlbodyelement

事件冒泡

假定事件在 element 上触发,然后事件会跟着下图一次冒泡

graph LR

Element-->body

body-->html

html-->document

事件是默认冒泡的,在 IE9,Opera9.5 及其他浏览器各版本及更高版本都是支持事件流的,但是上述明确浏览器的更低版本则不支持。

<body>

<div id="testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

</body>

<script>

/* 虽然 id 可以直接拿来使用,但是并不提倡,这不符合规范! */

window.onclick = function () {

console.log(this);

};

document.onclick = function () {

console.log(this);

};

document.body.parentNode.onclick = function () {

console.log(this);

};

document.body.onclick = function () {

console.log(this);

};

testElement.onclick = function () {

console.log(this);

};

</script>

控制台输出的结果:

index.html:29 <div id="testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

index.html:26 <body>…</body>

index.html:23 <html lang="en"><head>…</head><body>…</body></html>

index.html:20 #document

index.html:17 Window{postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window,…}

如果我们把 this 全价格 toString 方法改为字符串!

index.html:29 [object HTMLDivElement]

index.html:26 [object HTMLBodyElement]

index.html:23 [object HTMLHtmlElement]

index.html:20 [object HTMLDocument]

index.html:17 [object Window]

事件捕获

同事件冒泡正好相反,它会按照事件流向下级结构传递:

graph LR

document-->html

html-->body

body-->element

而控制他们事件流传递方向的创建事件监听时的一个参数,后面我再详细解释。

创建和移除事件监听

创建事件监听 addEventListener()方法

testElement.addEventListener("click",function(event) {

console.log(this);

console.log(event.type);

},false);

/*

index.html:32 <div id"testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

index.html:33 click

*/

它的第三个参数默认为 false,代表默认使用事件冒泡规则,同时事件会根据冒泡规则一次向上传递,如果改为 true 则事件采用事件捕获规则,根据文档结构和元素关系进行事件的向下传递。

事件的移除 removeEventListener()方法

testElement.removeEventListener("click",function(event) {

console.log(this);

console.log(event.type);

},false);

但是我们要注意的地方是,事件移除程序移除的是第二参数传递进去的函数,如果是函数体,会因为指向问题使移除监听并不算成功,而我们如果传递进去的是函数名的话,则能很好的达到效果!没听懂,没关系,看下面:testElement.addEventListener("click",function(event) { //它没有被取消掉

console.log(this);

console.log(event.type);

},false);

testElement.removeEventListener("click",function(event) { //它没有效果

console.log(this);

console.log(event.type);

},false);

/*

index.html:32 <div id="testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

index.html:33 click

*/

没错,remove 里的没有执行,但是 add 里的却仍然很好的执行了!那我们来看正确的方式!

testElement.addEventListener("click", handler, false);

testElement.removeEventListener("click", handler, false);

function handler(event) {

console.log(this)

console.log(event.type);

}

/*

控制台你什么也不会看到!

*/

究其原因,是因为上面的创建和移除是各自创建的函数,它们看起来很像,但是根本没有丝毫”血缘”关系,它们在内存中占据了两个不同的位置,而后面引用的函数名确实代表的同一个”东西”,它们指向的是内存中的同一个位置!!!

阻止事件的冒泡或者捕获、默认行为

阻止事件流的传递,stopPropagation()方法,为了方便我这里不用规范的创建监听方法了:

<body>

<div id="testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

</body>

<script>

/* 虽然 id 可以直接拿来使用,但是并不提倡,这不符合规范! */

window.onclick = function () {

console.log(this);

};

document.onclick = function () {

console.log(this);

};

document.body.parentNode.onclick = function () {

console.log(this);

};

document.body.onclick = function () {

console.log(this);

};

testElement.onclick = function (event) {

console.log(this);

console.log(event.type);

event.stopPropagation(); //阻止了事件冒泡

};

</script>

/*

index.html:29 <div id="testElement" style="background-color: greenyellow;width: 100px;height: 100px;"></div>

index.html:30 click

*/

如果要测试事件捕获需要用规范的创建事件监听,并使第三个参数改为 true 就可以了!

阻止默认行为发生

说到默认行为,就像 a 标签点击了会默认跳转到 href 一样,那么我们可以通过使用 preventdefault()方法实现,但是前提条件是对象的属性 cancelable 属性必须设置为 true 才可以阻止该对象的默认行为,使用时同时用 event 对象来调用!大家可以自行测试,这里就不赘述了。

事件委托

内存和性能:在一门编程语言中,给一个控件或者目标添加看似是司空见惯的事,但是却有着非比寻常的。在包含 GUI 编程的语言里,给每一个控件添加事件都是没有问题的,因为它编译的时候有一些特殊的事情要做,像我学过的 C#/.NET 中就是这样,不过 js 和众多桌面语言中有着很大的却别,因为 js 是个单线程的语言,不能像桌面语言那样 new Thread()来继续做自己的事,而是必须等待!事件在整个编译运行过程中也是遵循排队等待的铁则!那么精简的事件监听程序则是一个 js 脚本是否性能良好的体现!

跨浏览器的事件监听程序

说起来高大上,说实在的就是因为浏览器对事件监听程序创建的差异,谷歌是 addEventListener(),IE 是 attachEvent(),或者其他浏览器支持用οnclick=handler(以点击事件为例)创建,而对于事件,谷歌可以直接用 click,而 IE 就必须用 onclick 等等,为了 js 程序能在不同浏览器都能跑起来,所以,一个整合的跨浏览器的事件监听创建程序非常必要!毕竟还有有很多不会升级电脑和软件的朋友存在,尤其那些还在用 xp 系统的办公电脑啊。。。

var EventUtil = {

addHandler: function (ele, type, handler) {

if (ele.addEventListener) {

ele.addEventListener(type, handler, false);

} else if (ele.attachEvent) {

ele.attachEvent("on" + type, handler);

} else {

ele["on" + type] = handler;

}

},

removeHandler: function (ele, type, handler) {

if (ele.removeEventListener) {

ele.removeEventListener(type, handler, false);

} else if (ele.detachEvent) {

ele.detachEvent("on" + type, handler);

} else {

ele["on" + type] = null;

}

},

getEvent: function (event) {

return event ? event : window.event; //低版本 IE 的 event 对象位置不同!

},

getTarget: function (event) {

return event.target || event.srcElement;//不同浏览器有不同的调取方法

},

preventDefault: function (event) {

if (event.preventDefault) {

event.preventDefault();

} else {

event.retrunValue = false;

}

},

stopPropagation:function(event){

if(event.stopPropagation){

event.stopPropagation();

}else{

event.cancelBubble();

}

}

};

//比如创建一个键盘监听事件

EventUtil.addHandler(document, "keyup", function (event) {

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

KeyUp(target, event.keyCode ? event.keyCode.toString() : "mouse");

});

什么是事件委托?

刚才我有提到,js 本就是一个简单的脚本语言,没有桌面编程语言的多线程,所以每一个对象都创建一个甚至多个事件监听是非常耗费性能的!那么我们怎么解决呢?就是事件委托,事件委托的中心思想就是,我给当前模块尽可能给最大的父级创建事件监听程序,然后通过父级对子级进行事件的分发,对子级拥有对应功能的事件进行响应!形象化就像是一个酒店的服务台,一个服务台发送一个点击事件,服务台将事件分发到需要触发该事件的对应房间,因为 js 的单线程机制,所有消息和事件都是在队列中排队的,所以没有“占线”这一说法!所有的事件都能正常地分发给对应目标,从而省下很多性能资源!

实现以下委托:

/*

事件处理程序创建了一个键盘监听事件,通过 EventUtil 对象的 getEvent 获取当前事件,然后通过 event 的特有属性,

event.target 获取到事件触发时的响应目标(直接获取到元素对象!),然后通过参数等调用相应需要实现的函数

*/

EventUtil.addHandler(document, "keyup", function (event) {

event = EventUtil.getEvent(event);

var target = EventUtil.getTarget(event);

KeyUp(target, event.keyCode ? event.keyCode.toString() : "mouse");

});

这里我把键盘事件放在 document 上,是为了键盘能在全局被监听到,使用时并不是对象越大越好,一般来说模块化里同一个子父级树里尽量按照更高的父级关系设置是最好的!


关键词:前端培训

用户头像

编程江湖

关注

IT技术分享 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
JS事件详解和js事件委托