js的事件学习笔记
0、参考
JavaScript高级程序设计(第三版),第13章
1、事件流
js
的事件流分为捕获和冒泡两类,目前主流的方式是使用冒泡,在特殊情况下才会启用捕获(比如这种需求:一个div
中有多个子元素,希望可以用鼠标在页面上拖动整个div
而不触发子元素的事件,就可以用事件捕获)
冒泡传播
考虑如下的DOM
结构,外面有3层div
盒子,最内部有一个button
按钮
<div class="box1">
<div class="box2">
<div class="box3">
<button class="btn">
点击按钮
</button>
</div>
</div>
</div>
为button
和div
分别注册事件,查看当点击按钮时会发生的执行结果
var box1 = document.getElementsByClassName('box1')[0];
var box2 = document.getElementsByClassName('box2')[0];
var box3 = document.getElementsByClassName('box3')[0];
var btn = document.getElementsByClassName('btn')[0];
btn.onclick = function () {
console.log('btn被触发')
};
box3.onclick = function () {
console.log('box3被触发')
};
box2.onclick = function () {
console.log('box2被触发')
};
box1.onclick = function () {
console.log('box1被触发')
};
执行结果:
btn被触发 m5-js.js:27:5
box3被触发 m5-js.js:32:5
box2被触发 m5-js.js:37:5
box1被触发 m5-js.js:41:5
事件捕获
依然考虑如下的DOM
结构
<div class="box1">
<div class="box2">
<div class="box3">
<button class="btn">
点击按钮
</button>
</div>
</div>
</div>
使用addEventListener
接口可以指定捕获或者冒泡阶段
var box1 = document.getElementsByClassName('box1')[0];
var box2 = document.getElementsByClassName('box2')[0];
var box3 = document.getElementsByClassName('box3')[0];
var btn = document.getElementsByClassName('btn')[0];
box1.addEventListener('click', function (evt) {
console.log('捕获阶段--box1被触发');
}, true);
box2.addEventListener('click', function (evt) {
console.log('捕获阶段--box2被触发');
}, true);
box3.addEventListener('click', function (evt) {
console.log('捕获阶段--box3被触发');
}, true);
btn.addEventListener('click', function (evt) {
console.log('捕获阶段--btn被触发');
}, true);
btn.addEventListener('click', function (evt) {
console.log('冒泡阶段--btn被触发');
}, false);
box1.addEventListener('click', function (evt) {
console.log('冒泡阶段--box1被触发');
}, false);
box2.addEventListener('click', function (evt) {
console.log('冒泡阶段--box2被触发');
}, false);
box3.addEventListener('click', function (evt) {
console.log('冒泡阶段--box3被触发');
}, false);
结果:
捕获阶段--box1被触发 m5-js.js:27:5
捕获阶段--box2被触发 m5-js.js:31:5
捕获阶段--box3被触发 m5-js.js:35:5
捕获阶段--btn被触发 m5-js.js:39:5
冒泡阶段--btn被触发 m5-js.js:43:5
冒泡阶段--box3被触发 m5-js.js:55:5
冒泡阶段--box2被触发 m5-js.js:51:5
冒泡阶段--box1被触发 m5-js.js:47:5
2、事件绑定--onclick接口
事件绑定有两个主要的接口,第一种接口:
button.onclick = fn
DOM 0
级,此类注册事件只会出现在冒泡阶段。
这种方式将事件触发接口(onclick)
作为元素的一个属性,值即为事件触发后执行的回调函数(fn)
。因为一个元素的一个属性只能指向一个值,故此类绑定方式中,元素只能注册一个同类的事件,旧的注册事件会被新的注册事件所覆盖,事件注销时,使用button.onclick = null
。
onclick类的接口,只能注册一个同类事件
考虑下面的DOM
结构
<button class="btn">点击按钮</button>
在按钮button
上先后绑定两个同类事件,后绑定的事件会覆盖前绑定的事件
var btn = document.getElementsByClassName('btn')[0];
btn.onclick = function (event) {
console.log('hello word')
};
btn.onclick = function () {
console.log('你好世界')
};
执行结果
你好世界 m5-js.js:32:5
onclick类的接口,使用button.onclick = null的方式注销事件
依然考虑如下的DOM
结构
<button class="btn">点击按钮</button>
在button
上绑定一个点击事件,然后启动一个定时器,在4s
之后注销此事件
var btn = document.getElementsByClassName('btn')[0];
setTimeout(function () {
btn.onclick = null;
}, 2000);
btn.onclick = function (event) {
console.log('hello word');
};
执行结果
在网页加载完毕后的2s
内,每次点击button
按钮都会打印hello world
,但是2s
之后点击就不再有反应。
hello word m5-js.js:31:5
3、事件绑定--addEventListener接口
事件绑定的第二种接口:
button.addEventListener('click', fn, false)
DOM 2
级,此类注册事件可以自选出现在捕获或者冒泡阶段。
这种事件触发接口(addEventListener)
作为元素的一个事件注册函数,参数是事件类型、回调函数、布尔值(true
为选择捕获/false
为选择冒泡,默认为false
)。此注册函数实现了类似'回调函数队列'的效果,一个元素可以注册多个同类的事件,当事件发生时,多个回调函数会依次执行。
事件注销时,使用button.removeEventListner('click', fn, false)
,参数的值必须和注册时指向的对象一致。
注意:使用removeEventListener
接口方式,无法处理匿名回调函数的事件注销。(因为匿名函数一旦创建,后续就无法获取对它的引用)
addEventListener接口,可以注册多个同类事件,发生时,依次执行回调函数
考虑如下DOM
结构
<button class="btn">点击按钮</button>
为button注册多个同类click事件,这些回调函数不会覆盖,而是会依次执行
var btn = document.getElementsByClassName('btn')[0];
btn.addEventListener('click', function (evt) {
console.log('hello func - 1');
}, false);
btn.addEventListener('click', function (evt) {
console.log('hello func - 2');
}, false);
btn.addEventListener('click', function (evt) {
console.log('hello func - 3');
}, false);
btn.addEventListener('click', function (evt) {
console.log('hello func - 4');
}, false);
执行结果
hello func - 1 m5-js.js:27:5
hello func - 2 m5-js.js:32:5
hello func - 3 m5-js.js:36:5
hello func - 4 m5-js.js:40:5
addEventListener接口无法注销以匿名函数注册的事件
考虑如下DOM
结构
<button class="btn">点击按钮</button>
button按钮注册了两个click
事件,其中一个指向有名回调函数sayHello
,一个指向匿名函数。2s
后尝试注销这两个事件,最终发现,有名函数的事件可以被注销,匿名函数的事件无法注销。
var btn = document.getElementsByClassName('btn')[0];
function sayHello() {
console.log('hello world')
}
// button注册了两个click事件,其中一个使用有名回调函数,一个使用匿名回调函数
btn.addEventListener('click', sayHello, false);
btn.addEventListener('click', function (evt) {
console.log('你好世界');
});
// 2s后尝试注销button的click事件
setTimeout(function () {
btn.removeEventListener('click', sayHello, false);
btn.removeEventListener('click', function (evt) {
console.log('你好世界');
});
}, 2000);
4、事件对象
每一次预定事件发生时,在回调函数中都可以直接引用event
对象,此对象代表了事件对象,包含事件发生的一些必要信息。事件对象中有两个关于目标的属性,event.currentTarget
代表着当前回调函数所属的对象,event.target
代表着触发事件的源对象。
考虑如下DOM
结构
<button class="btn">点击按钮</button>
为button
按钮绑定一个事件,在回调函数中可以直接调用event
对象
var btn = document.getElementsByClassName('btn')[0];
btn.onclick = function (event) {
console.log(event.currentTarget === this);
console.log(event.target === btn);
};
执行结果
true m5-js.js:27:5
true m5-js.js:28:5
this
和event.currentTarget
总是同样的引用,代表的是回调函数所属的对象。
event.target
可以获取发生事件的源头对象。
5、阻止冒泡传播和阻止默认行为
冒泡类事件流,默认情况下会将事件传播至所有父级元素,但在某些时候我们需要主动停止冒泡事件流的传播。
考虑如下DOM
结构
<div class="box1">
<button class="btn">点击按钮</button>
</div>
为button
和box1
均注册点击事件,默认情况下,button
的点击事件也会触发box1
的点击行为。但如果在button
的回调函数中执行event.stopPropagation()
就可以阻止冒泡事件的向上传播。
var box1 = document.getElementsByClassName('box1')[0];
var btn = document.getElementsByClassName('btn')[0];
box1.onclick = function () {
console.log('box1发生了click事件');
};
btn.onclick = function (event) {
console.log('btn发生了click事件');
event.stopPropagation();
};
执行结果
btn发生了click事件 m5-js.js:30:5
某些html
元素含有默认的事件行为,某些时候我们也需要主动停止默认行为的发生。
考虑如下DOM
结构
<div class="box1">
<a href="http://www.baidu.com" class="link">点击去往百度</a>
</div>
当点击a
元素的时候,会执行默认行为,即前往指定的herf
地址。但是如果使用event.preventDefault()
即可阻止默认行为的发生。如下执行结果并不会跳转到指定href
页面。
var a = document.getElementsByClassName('link')[0];
a.onclick = function (event) {
console.log('连接a发生了点击事件,取消默认行为');
event.preventDefault();
};
执行结果
连接a发生了点击事件,取消默认行为 m5-js.js:36:5
有时候,我们希望既可以阻止冒泡事件的传播,同时也可以阻止默认行为,那么就可以使用return false
。
6、事件带来的问题
第一个问题在于页面上会出现过多数量的事件,这些事件的回调函数都是需要占用内存,事件越多占用的内存也越大。某些事件有重复的情况,比如多个同类的li
标签可能绑定着同样的事件回调函数,这种事件触发的方式效比较低,还可以进一步优化,以上问题可以使用事件委托(代理)。
第二个问题在于如何妥善的处理事件的注销。比如元素在发生innerHTML
修改或者被remove
节点时,对应的回调函数也会失去引用,这些失去引用的事件可能无法被垃圾回收机制正确回收。所以在处理一个含有事件的元素时,应特别注意事件的注销操作,使用onclick = null
或者使用removeEventListener
。
7、事件委托(代理)
事件委托技术依赖于事件流冒泡传播。同类的子元素的事件发生,不应该在子元素上注册事件,而应该在共同的父元素上注册事件,由父元素来处理子元素的事件发生,此父元素即为子元素的事件委托者或者代理者。
考虑如下DOM
结构
<ul class="item-list">
<li class="item">选项1</li>
<li class="item">选项2</li>
<li class="item">选项3</li>
<li class="item">选项4</li>
<li class="item">选项5</li>
<li class="item">选项6</li>
<li class="item">选项7</li>
<li class="item">选项8</li>
<li class="item">选项9</li>
<li class="item">选项10</li>
</ul>
使用事件代理,只需要在父元素上绑定事件,所有子类元素的事件均会通过冒泡的形式传播到父元素的回调函数中处理。
var itemList = document.getElementsByClassName('item-list')[0];
itemList.onclick = function (event) {
// 利用冒泡原理来实现事件代理
var origin = event.target;
var originText = origin.innerText;
console.log('发生点击事件的元素是:', originText);
};
事件委托技术减少了多个同类子元素的重复事件注册,减少了内存的开销,事件处理的效率也更高。
此外,事件委托还有一个好处是不用关心新增子元素的事件注册操作,新增子元素只需要加入父元素即可,不必再次执行事件注册。
父元素也可以通过switch
判断子元素的方式来对不同的子元素执行相应的处理函数。
考虑如下的DOM
结构
<div class="item-list">
<button class="item">选项1</button>
<li class="item">选项2</li>
<a href="#" class="item">选项3</a>
</div>
父元素执行事件代理,同时通过switch
判断子元素的tagName
,针对不同的标签执行不同的事件处理。
var itemList = document.getElementsByClassName('item-list')[0];
itemList.onclick = function (event) {
// 利用冒泡原理来实现事件代理
var origin = event.target;
switch (origin.tagName) {
case 'BUTTON':
console.log('点击的是一个按钮');
break;
case 'LI':
console.log('点击的是一个列表项目');
break;
case 'A':
console.log('点击的是一个链接');
break;
}
};
执行结果
点击的是一个按钮 m5-js.js:26:13
点击的是一个列表项目 m5-js.js:29:13
点击的是一个链接 m5-js.js:32:13
8、模拟事件触发
在jQuery
中,提供了trigger
接口作为模拟事件的触发,此接口让我们非常方便的触发指定元素的指定事件。
考虑如下DOM
结构
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
为两个button
都注册对应的事件,在button1
的回调函数中,执行button2
的trigger
函数,触发button2
的点击事件。点击button1
后,即使在没有点击button2
的情况下,也会执行button2
的回调函数。
var $btn1 = $('#btn1');
var $btn2 = $('#btn2');
$btn1.bind('click', function () {
console.log('btn1被点击');
$btn2.trigger('click');
});
$btn2.bind('click', function () {
console.log('btn2被点击');
});
执行结果(只点击了button1
)
btn1被点击 m5-js.js:21:5
btn2被点击 m5-js.js:26:5
在原生JS中,需要使用createEvent
接口和dispatchEvent
接口实现同样的效果。
考虑如下DOM
结构
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
点击button1
的时候,触发button2
的click
事件
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, document.defaultView, 0, 0, 0, 0, 0,
false, false, false, false, 0, null);
btn1.onclick = function () {
console.log('btn1被点击');
btn2.dispatchEvent(event);
};
btn2.onclick = function () {
console.log('btn2被点击');
};
执行结果(只点击了button1
)
btn1被点击 m5-js.js:24:5
btn2被点击 m5-js.js:29:5
9、总结
事件是js
中非常重要的模块,它完成了js
代码和html
代码之间的互动,通过事件可以提供丰富的高体验性用户交互,可以说通过js
实现的用户交互就是以事件作为驱动的。js
的事件模型为:目标元素+事件对象+回调函数。
js的事件学习笔记的更多相关文章
- 浏览器中js执行机制学习笔记
浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...
- 纯JS实现KeyboardNav(学习笔记)一
纯JS实现KeyboardNav(学习笔记)一 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 GitHub项目源码 预览地址 最终效果 KeyboardNav使用指南 ...
- 纯JS实现KeyboardNav(学习笔记)二
纯JS实现KeyboardNav(学习笔记)二 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 这篇主要是添加css,优化js编写逻辑和代码排版 GitHub项目源码 ...
- 【09-23】js原型继承学习笔记
js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- C#委托与事件学习笔记
委托事件学习笔记 本文是学习委托和事件的笔记,水平有限,如有错漏之处,还望大神不吝赐教. 什么是委托?从字面意思来解释,就是把一个动作交给别人去执行.在实际开发中最常用的就是使一个方法可以当做一个参数 ...
- 《JS高程》事件学习笔记
事件:文档或浏览器窗口中发生的一些特定的交互瞬间,也即用户或浏览器自身执行的某种动作. -------------------------------------------------------- ...
- [360前端星计划]BlackJack(21点)(纯JS,附总部学习笔记)
[360前端星计划]总部学习笔记(6/6) [360前端星计划]详情跳转 游戏界面预览 目录 一.游戏介绍 1.起源 2.规则 3.技巧 二.游戏设计 1.整体UI构思 2.素材采集 3.游戏总规划 ...
随机推荐
- flex 分页
<?xml version="1.0" encoding="utf-8"?><s:Group xmlns:fx="http://ns ...
- pyhthon lambda
lambda x:x+1(1) >>>2 可以这样认为,lambda作为一个表达式,定义了一个匿名函数,上例的代码x为入口参数和出口参数,x+1为函数体,(1)为x的入口初始值, 用 ...
- MongoDB安装为Windows服务方法与注意事项
MongoDB作为一个基于分布式文件存储的数据库,近两年大受追捧.数据灵活的存取方式和高效的处理使得它广泛用于互联网应用. 最近本人开始在Windows 32位平台下研究MongoDB的使用,为了方便 ...
- Restful风格wcf调用4——权限认证
写在前面 在前面的三篇文章,已经介绍了restful风格wcf,如何实现增删改查以及文件的上传下载操作.本篇文章将介绍一下,调用restful的权限认证的内容.在调用的接口,为了安全,总会需要对请求进 ...
- 硬盘坏道检测工具对比(DiskGenius/HdTunePro/MHDD等)
说到硬盘检测软件,大家肯定会想到MHDD,但是MHDD真的好用?反正我觉得太难用了,只能在DOS下运行,不能在Win系统下运行:最重要的是只支持IDE硬盘模式,现在的主板几乎全部默认都是AHCI模式, ...
- hdu 4982 贪心构造序列
http://acm.hdu.edu.cn/showproblem.php?pid=4982 给定n和k,求一个包含k个不相同正整数的集合,要求元素之和为n,并且其中k-1的元素的和为完全平方数 枚举 ...
- FFmpeg命令详解
命令格式 功能 FFmpeg命令是在ffmpeg.exe可执行文件环境下执行,ffmpeg.exe用于音视频的转码,加水印,去水印,视频剪切,提取音频,提取视频,码率控制等等功能. 最简单的命令 ff ...
- CC2530学习路线-基础实验-定时器控制LED灯亮灭(3)
目录 1. 前期预备知识 1.1 定时器中断触发 1.2 相关寄存器 1.3 寄存器相关问题 1.4 T1.T3定时器初始化流程 2 程序及代码 THE END 1. 前期预备知识 1.1 定时器中断 ...
- Java的8种基本数据类型
待整理主题:Java的8种基本数据类型与对应封装类型.拆箱.装箱 =================================================================== ...
- FunDA(12)- 示范:强类型数据源 - strong typed data sources
FunDA设计的主要目的是解决FRM(Functional Relation Mapping)如Slick这样的批次型操作工具库数据源行间游动操作的缺失问题.FRM产生的结果集就是一种静态集合,缺乏动 ...