target和currentTarget
代码:
// 当用户点击文字的时候
<div> // e.currentTarget就是div
<span>文字</span> // e.target就是span
</div>
复制代码
e.target
: 用户操作的元素e.currentTarget
: 程序员监听的元素this
为e.currentTarget
, 是不推荐的用法,因为this
指向不确定
特例: 在同级别div
中
div.addEventListener('click', f1) // f1先被执行
div.addEventListener('click', f2, true) // 然后被f2执行
复制代码
请问, f1
还是f2
先执行?如果把代码调换顺序,哪个先被执行?
答案:谁先事件监听,谁就先执行。但是这是一个特例。
特例存在的情况:
- 在不考虑父子同时被监听的情况下,只有一个
div
被监听 fn
会分别在捕获阶段和冒泡阶段监听click
事件- 开发者监听的元素就是用户点击的元素
事件绑定
- 直接获取元素绑定
优点是:简单和稳定,可以确保它在你使用的不同浏览器中运作一致。
缺点:只会在事件冒泡中运行;一个元素一次只能绑定一个事件处理函数,新绑定的事件处理函数会覆盖旧的事件处理函数;事件对象参数(e
)只有在非IE
浏览器才可用
element.onclick = function(e){
// ...
};
复制代码
- 直接在元素里面使用事件属性
<button οnclick="f1"></button>
复制代码
3、添加事件监听
w3c
方法
优点:该方法同时支持事件处理的捕获和冒泡阶段;事件阶段取决于addEventListener
最后的参数设置:false
(冒泡) 或 true
(捕获);在事件处理函数内部。事件对象总是可以通过处理函数的第一个参数(e
)捕获;可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件
缺点:IE
不支持,你必须使用IE
的attachEvent
函数替代
element.addEventListener('click', function(e){
// ...
}, false);
复制代码
IE
方法
优点:可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。
缺点:IE
仅支持事件捕获的冒泡阶段;如果使用了this
,事件监听函数内的this
关键字指向了window
对象,而不是当前元素, 事件对象仅存在与window.event
参数中;事件必须以ontype
的形式命名,比如,onclick
而非click
;仅IE
可用,你必须在非IE
浏览器中使用W3C
的addEventListener
注意:不是意味着版本的IE
没有事件捕获,它也是先发生事件捕获,再发生事件冒泡,只不过这个过程无法通过程序控制。
element.attachEvent('onclick', function(){
// ...
});
复制代码
取消事件绑定
- 使用
removeEventListener
- 使用
detachEvent
// W3C
element.removeEventListener('click', function(e){
// ...
}, false);
// IE
element.detachEvent('onclick', function(){
// ...
});
复制代码
取消冒泡
- 捕获不可以被取消,冒泡可以被取消
- 在支持
addEventListener()
的浏览器中,可以调用事件对象的stopPropagation()
方法以阻止事件的继续传播。如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用stopPropagation()
之后任何其他对象上的事件处理程序将不会被调用。即可以阻止事件在冒泡阶段的传播 IE 9
之前的IE
不支持stopPropagation()
方法,而是设置事件对象cancelBubble
属性为true
来实现阻止事件进一步传播- 有些事件不可以取消冒泡,例如
MDN
搜索scroll event
, 看到Bubbles
和Canceleble
, 在Canceleble
可以找到不可以取消冒泡的事件
// w3c
element.addEventListener("click", function(e){
// 在捕获阶段阻止事件的传播
e.stopPropagation();
}, true);
复制代码
阻止滚动
scroll
事件为不可取消的冒泡事件,阻止scroll
默认动作没有用,因为先有滚动才有滚动事件。要阻止滚动,需要阻止wheel
和touchstart
的默认动作CSS
上使用overflow:hidden
可以直接取消滚动条,然而JS
上依然可以修改scrollTop
element.addEventListener('wheel',(e)=>{
e.preventDefault() // 取消滚轮的默认动作
})
element.addEventListener('touchstart',(e)=>{
e.preventDefault() // 取消手机上的触屏默认动作
})
复制代码
取消默认行为
e.preventDefault()
可以阻止事件的默认行为发生,默认行为是指:点击a
标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为IE 9
之前的IE
中,可以通过设置事件对象的returnValue
属性为false
达到同样的效果
function cancelHandler(event){
var event=event||window.event;//兼容IE
//取消事件相关的默认行为
if(event.preventDefault) //标准技术
event.preventDefault();
if(event.returnValue) //兼容IE9之前的IE
event.returnValue=false;
return false; //用于处理使用对象属性注册的处理程序
}
复制代码
自定义事件
浏览器自带事件,一共有100
多种事件,可以在MDN
上查询。同时,开发者也可以在自带事件之外,自定义一个事件
// html代码
<body>
<div id=div1>
<button id=button1>点击触发事件
</button>
</div>
**************
// js代码
// 先自定义事件,然后点击时触发自定义事件
button1.addEventListener('click', ()=>{
const event = new CustomEvent("tab", {"detail":{name:'tab', age: 18}},
bubbles: true) // 允许冒泡
button1.dispatchEvent(event)
})
// 监听自定义事件
button1.addEventListener('tab', (e)=>{
console.log('tab')
console.log(e)
})
复制代码
事件委托
定义
- 在
JavaScript
中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM
访问次数,会延迟整个页面的交互就绪时间。 - 对事件处理程序过多问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,
click
事件会一直冒泡到document
层次。也就是说,我们可以为整个页面指定一个onclick
事件处理程序,而不必给每个可单击的元素分别添加事件处理程序 - 优点:提高页面性能,可以监听动态元素
事件委托使用场景
- 实例一:假设给
100
个按钮添加点击事件
// html代码
<div id="div1">
<button data-id="1">1</button>
<button data-id="2">2</button>
<button data-id="3">3</button>
<button data-id="4">4</button>
****************
</div>
// js代码
div1.addEventListener('click', (e)=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('button'被点击了)
console.log('button'内容是 + t.textContext) // 获取被点击元素的文本内容
console.log('button 的data-id是:'+ t.dataset.id) // 获取被点击元素的dataset.id
}
})
复制代码
- 实例二:监听目前不存在的元素的点击事件,例如下面的例子中
1
秒钟之后button
才出现
// html代码
<div id="div1">
</div>
// js代码
setTimeout(()=>{
const button = document.createElement('button')
button.textContent= 'click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click', (e)=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('button'被点击了)
}
})
复制代码
封装事件委托
写出一个函数,例如on('click', '#div1','li', fn)
,当用户点击div1
中的li
时,调用fn
函数
// 答案一
setTimeout(()=>{
const button = document.createElement('button')
button.textContent= 'click 1'
div1.appendChild(button)
},1000)
functin on(eventType, element, selector, fn){
if(!(element instanceOf Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType, (e)=>{
const t = e.target
if(t.matches(selector)){
fn(e)
}
})
}
on('click', '#div1', 'button',()=>{
console.log('button 被点击了')
})
复制代码
// 答案二: 使用递归进行判断
function on(eventType, element, selector, fn) {
if(!(element instanceOf Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
复制代码
注意的是:本章节讲的是DOM
的事件,JS
只是调用了DOM
提供的addEventListener
方法,其实JS
不支持事件