读书笔记(05) - 事件 - JavaScript高级程序设计

coding

HTML依托于JavaScript来实现用户与WEB网页之间的动态交互,接收用户操作并做出相应的反馈,而事件在此间则充当桥梁的重要角色。

日常开发中,经常会为某个元素绑定一个事件,编写相应的业务逻辑,在元素被点击时执行,并反馈到用户操作界面。

这个过程中,事件就像一个侦听器,当点击动作发生时,才会执行对应的程序。这种模式可称之为观察员模式。

接下来就讲讲DOM事件相关知识。

何为事件

事件就是用户或浏览器自身执行的某种动作

常用的DOM事件有click/mouseover/mouseout/keyup/keydown等。

事件流

事件流描述的是从页面中接收事件的顺序

HTML描述的是一个DOM文档结构,而事件流所描述的是DOM文档节点接收事件顺序。

而事件流有两种事件模式,捕获/冒泡,两者所描述的事件传递顺序对立相反。

事件模式:捕获与冒泡

冒泡

事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)

规范要求事件冒泡到document对象,而浏览器则会将事件一直冒泡到window对象。

所有浏览器都支持事件冒泡(包括IE9以下)。

事件冒泡
捕获

事件捕获:(与事件冒泡相反)事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件

与冒泡一样,虽然规定事件应该从document对象开始传播,但浏览器普遍都是从window对象开始捕获。

IE9以下不支持事件捕获

事件捕获

DOM 事件流

"DOM2级事件"规定事件流包括三个阶段,顺序进行

  1. 事件捕获阶段
  1. 处于目标阶段
  1. 事件冒泡阶段

TIPS: 实际的目标元素在捕获阶段不会接收到事件,在处于目标阶段时接收事件发生处理,并被看成是冒泡阶段的一部分。

尽管"DOM2级事件"规范明确要求捕获阶段不会涉及事件目标,但浏览器会在捕获阶段触发事件对象上的事件。

事件处理程序

响应某个事件的函数方法,我们称之为事件处理程序(或事件侦听器)

window.onclick = function() {
    //...
}
// 这里的function(){}就是事件处理程序

HTML事件处理程序

HTML中元素支持的事件,可以使用一个同名的HTML特性来指定,而这个特性的值就是js能执行的代码或表达式。写法上可以看出类似HTML中id/type/class等属性的写法,都是on+'...'


缺点:HTML是结构层(显示层),而JavaScript是行为层(业务层)。在显示层上去编写业务逻辑代码处理,会使得HTML与JavaScript代码耦合过于紧密,不好维护。

DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。

DOM事件分为3个级别:DOM 0级事件处理程序,DOM 2级事件处理程序和DOM 3级事件处理程序。DOM 1级中没有规范事件的相关内容,所以没有DOM 1级事件处理。

DOM0 级事件处理程序

每个元素(HTML元素)都有自己的事件处理程序属性,属性名通常以on开头,例如onclick/onmouseover。为这个属性的值设置一个函数,就可以指定事件处理程序。而将其属性值赋值为null,则完成解绑。(同个元素无法绑定多个同名事件)

var myBtn = document.getElementById('myBtn');

// 为myBtn绑定事件处理程序, 只能绑定一个
myBtn.onclick = function() {
    alert('Hello world!');
}

// 解绑
myBtn.onclick = null;

DOM2 级事件处理程序

"DOM2级事件"定义了两个方法,addEventListener()/removeEventListener(),用于为元素绑定和解绑事件。

可绑定多个事件,区别于DOM0级/HTML仅能绑定一个)。

el.addEventListener(eventName, callBack, useCapture)

  • eventName: 事件名称

  • callBack: 回调函数,当事件触发时,函数会传入一个参数event,为当前的事件对象

  • useCapture: 默认是false,代表事件句柄在冒泡阶段执行, true则代表在捕获阶段执行

var myBtn = document.getElementById('myBtn');

var handleClick = function() {
    alert('Hello world!');
}
// 绑定事件处理程序
myBtn.addEventListener('click', handleClick, false);

// 解绑
myBtn.removeEventListener('click', handleClick);

TIPS:DOM2级事件处理程序,解绑时function必须与传入addEventListener相同

// 绑定
myBtn.addEventListener('click', function() {
    // 匿名函数
});

// 解绑
myBtn.removeEventListener('click',function() {
    // 匿名函数
});

// add/remove 分别绑定了两个匿名函数(函数为引用类型),所以两个函数并不相同,所以无法成功解绑

TIPS:绑定多个事件处理程序时,执行顺序按绑定顺序执行

myBtn.addEventListener('click', function() {
    // step1...
})
myBtn.addEventListener('click', function() {
    // step2...
})

// 执行顺序:step1 -> step2

浏览器支持情况:IE9以下不支持DOM2级事件处理程序

IE 事件处理程序

IE9以下不支持DOM2级事件,但IE提供了与DOM2级事件类似的两个方法,attachEvent()/detachEvent,IE9以下不支持事件捕获,所以attachEvent仅支持冒泡阶段触发,只接收两个参数(eventName, function)。

// 绑定
myBtn.attachEvent('onclick', handleClick);

// 解绑
myBtn.detachEvent('onclick', handleClick);

TIPS:

  1. 解绑时function必须与传入attachEvent相同,这点与DOM2级事件相同

  2. 与DOM0级的区别,DOM0级事件处理在元素的作用域运行,而attachEvent事件处理在全局,this指向window

  3. 绑定多个事件处理程序时,执行顺序按绑定顺序逆反执行(与DOM2级相反)

myBtn.attachEvent('click', function() {
 // step1...
})
myBtn.attachEvent('click', function() {
 // step2...
})
// 执行顺序:step2 -> step1

Event 事件对象

常见应用

event.preventDefault()

阻止默认事件

event.stopPropagation()

阻止事件流发生传递(冒泡/捕获)

event.stopImmediatePropagation()

阻止剩余事件处理函数的执行,并阻止当前事件在事件流上传递

event.currentTarget

当前绑定事件的元素

event.target

当前触发事件的元素

event.stopPropagation()与.stopImmediatePropagation()的区别

同个元素绑定多个同名事件时,stopImmediatePropagation不仅阻止了冒泡,而且会阻止后续事件的执行,可以理解为加强版的stopPropagation

myBtn.addEventListener('click', function(event) {
    // step1;
    event.stopImmediatePropagation();
})

myBtn.addEventListener('click', function(event) {
    // step2;
    // 我被stopImmediatePropagation阻止掉了!!!
})
currantTarget与target的区别

事件处理程序内部,this等于currentTarget(当前绑定事件的元素),而target(当前触发事件的元素)

// currentTarget == target
myBtn.addEventListener('click', function(event) {
    event.target == event.currentTarget; // true -> myBtn
})

// currentTarget != target 捕获/冒泡
document.body.addEventListener('click', function(event){
    event.target == event.currentTarget; // false
    // event.target -> myBtn
    // event.currentTarget -> body
})

内存与性能

WEB网页是运行在浏览器客户端的,而计算机分配给浏览器的内存及CPU占用是有限制的。虽说浏览器引擎不断地发展优化,但是内存占用多了, 性能不免会损耗。

内存

为元素指定事件绑定程序,事实上是赋值了一个函数方法,而函数在javaScript中是一种引用类型的数据格式,既然是数据那就需要用到内存储存。函数创建多了,消耗掉内存。

性能

为元素指定事件绑定程序,首先需要对DOM进行查询,找出要绑定事件的元素。而这也会造成DOM元素的访问次数增加。DOM的操作一直是网页性能的一个优化点。

了解完事件绑定带来内存跟性能的原理,我们来看一个例子,例如我们有一个ul>li的列表,要监听每一个li的点击事件,并触发事件处理程序。

单独绑定的话,10个li就要对DOM元素查询10次,创建的匿名函数就有10个(当然可以共同创建同个函数引用),如果还有20个,30个,100个,那么这种为每个li元素单独绑定事件的方法,绝对不是最优解。

这就引出下面的优化方案:"事件委托"

事件委托(事件代理)

对"事件处理程序绑定过多"的问题,最好的解决方案就是"事件委托"。它的原理是利用了事件流的"冒泡"机制,事件目标元素会把事件向上层传递,直到document(浏览器会传到window),所以父级节点是可以接收子节点的事件传递。

以刚刚ul>li的例子,li有很多个, 但它们有一个共同的父节点ulli的点击事件会冒泡到ul,因此我们可以在ul上绑定一个事件处理程序,处理所有li的点击事件,然后通过event.target可以确定触发事件的元素。

var ulParent = document.getElementById('parent');
ulParent.addEventListener('click', function(event) {
    var taget = event.target; 
})

通过"事件委托"减少了DOM元素的查询,以及多个函数的内存占用,而且还有一个好处,当我们的li是动态的,增加和移除时,都无需再做绑定和解绑事件操作,因为它都会冒泡到父级节点。

移除多余的事件绑定

文档中移除了绑定了事件的DOM元素,如innerHTML/removeChild()/replaceChild()等可以对DOM进行替换,而移除的DOM元素原先所绑定的事件处理程序,并不能有效被浏览器垃圾回收,所以占用一直存在。

所以建议在移除某个DOM元素时,如果其绑定了事件处理程序,需手动解除绑定,释放内存。

自定义事件

除了为元素绑定支持的事件以外,我们还可以通过Event/CustomEvent来创建开发者自定义事件。

两者不同的是CustomEvent可传递一个Object对象来传输数据。

// Event
var eve = new Event('custome');

// CustomeEvent 可传参数
var eve = new CustomeEvent('custome', {
    detail: {
        name: 'KenTsang',
        age: 28
    }
});

// 为DOM元素添加事件监听
ele.addEventListener('custome', function(event) {
    console.log(event.detail);
})

// 触发ele绑定的自定义事件
ele.dispatch(eve);

事件这块还剩下一部分知识点,后续文章会再就模拟事件这块知识点进行拆分详解。

天冷了,更文不易,望大家多多点赞。

作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容