一、事件模型
事件模型就是通过监听事件执行对应的监听函数。
JS添加事件有三种方法:
- 通过html的on属性直接添加事件监听代码,监听事件只在冒泡阶段触发
<script>
el.setAttribute('onclick','doSomething()');
//等同于<Element onclick = "doSomething()">
// 错误写法: <Element onclick = "doSomething">
</script>
<div onclick="console.log(2)">
<button onclick="console.log(1)">click here</button>
</div>
所以点击button时先输出1,再输出2 ,
缺点:法违反了 HTML 与 JavaScript 代码相分离的原则,将两者写在一起,不利于代码分工
- 节点对象事件属性,监听事件只在冒泡阶段触发
window.onload = doSomething;
div.onclick = function (event){}
优点:绑定事件可以使JavaScript代码与HTML标签分离,文档结构清晰,便于管理和开发。
缺点:同一个事件只能定义一个监听函数,如果定义两次onclick属性,后一次定义会覆盖前一次。
注意:它的值是函数名(doSomething),html on 属性的值(doSomething())
- 事件监听
window.addEventListener('load',doSomething,false)
语法:addEventListener(event, function, useCapture)
event:事件名
function:触发事件回调函数
useCapture:true,捕获。false,冒泡。默认false。
IE8以下不支持。
IE语法:element.attachEvent(event, function)
event:事件类型。需加“on“,例如:onclick。
function:触发事件回调函数
优点:
- 同一个事件可以添加多个监听函数。
- 可以解除相应的绑定 removeEventListener(event,function);
- 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
- 它是JavaScript 统一的监听函数接口,支持事件的任何对象(例如XMLHttpRequest)
封装事件监听
<input type="button" value="click me" id="btn5">
<script>
//绑定监听事件
function addEventHandler(target,type,fn){
if(target.addEventListener){
target.addEventListener(type,fn);
}else{
target.attachEvent("on"+type,fn);
}
}
//移除监听事件
function removeEventHandler(target,type,fn){
if(target.removeEventListener){
target.removeEventListener(type,fn);
}else{
target.detachEvent("on"+type,fn);
}
}
//测试
var btn5 = document.getElementById("btn5");
addEventHandler(btn5,"click",hello1);//添加事件hello1
addEventHandler(btn5,"click",hello2);//添加事件hello2
removeEventHandler(btn5,"click",hello1);//移除事件hello1
</script>
事件的传播分成三个阶段:
- 捕获阶段(capture): 从window对象传导到目标节点
- 目标阶段(target):在目标节点上触发
- 冒泡阶段(bubbling):从目标节点传导回window对象
这三个阶段到传播模型,使得同一事件可以触发所经过节点对应的事件监听
<div>
<p>点击</p>
</div>
如果<div>
和 <p>
两个节点都设置了click事件监听函数(捕获冒泡阶段各设置一个监听函数),对<p>
点击,click事件会触发四次。
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
this指向
监听函数内部的this指向触发事件的那个元素节点。
<!-- 写法一 -->
<button id="btn1" οnclick="console.log(this.id)">点击</button>
<script>
var btn = document.getElementById('btn');
// 写法二
btn.onclick = function () {
console.log(this.id);
};
// 写法二
btn.addEventListener(
'click',
function (e) {
console.log(this.id);
},
false
);
// 以上三种方法都是输出 btn
<script>
三、事件对象都有哪些属性,分别应用于哪些场景
事件属性指的是事件所引发的状态以及事件本身特有的一些性质。
//获取事件对象
var even = event || window.event;
//事件方法
event.preventDefaule() //阻止默认的事件行为,如链接的跳转、表单的提交;
event.stopPropagation() //阻止事件冒泡
event.target() //获取触发事件的元素
event.stopImmediatePropagation()
//取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(没用过)
event.type //获取事件类型
event.currentTarget //获绑定事件的元素
event.target //获触发事件元素
event.timeStamp//事件发生的时间
event.keyCode //获取键盘unicode
event.which //获取鼠标的左、中、右键(1表示左键,2表示中键,3表示右键)
event.button //获取鼠标的左、中、右键(1表示左键,2表示中键,3表示右键)
event.eventPhase
//获取事件传播的当前阶段:1表示捕获阶段,2表示处于目标阶段,3表示冒泡阶段
//获取鼠标距离左上角x,y坐标
even.offsetX,even.offsetY //相对元素
even.clientX,even.clientY //相对浏览器
event.pageX event.pageY//相对页面
even.screenX ,even.screenY //相对屏幕
layerX/Y //firefox用来替代offsetX/Y
四、事件委托是什么?有哪些优势,应用场景
事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。
优点:
- 减少DOM操作提高事件处理速度,减少事件注册,节省内存。
添加到页面到事件越多,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,性能优化的主要思想之一就是减少DOM操作。通过事件委托,与DOM交互一次可以处理多个事件程序,大大减少DOM交互次数。
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了。如果要对100个li对象绑定事件,就要占用100个内存,通过事件委托,不需要去遍历元素的子节点,对一个ul对象进行操作,只需要一个内存,性能自然大大提高。 - 动态的添加DOM元素,不需要因为元素的改动而修改事件绑定。
五、习题
document.querySelector('label').addEventListener('click',function () {
console.log(1)
})
document.querySelector('input').addEventListener('click',function () {
console.log(2)
})
先打印 2,再打印 1.
事件监听默认为冒泡触发监听函数,冒泡传播方向是从子节点到父节点,所以先触发input绑定事件再触发label绑定事件