关于JavaScript中的事件
关于JavaScript中的事件
- 1.事件和事件流(事件传播)
- 1.1什么是事件
- 1.2什么是事件流
- 2.事件冒泡
- 2.1事件冒泡是什么
- 2.2阻止事件冒泡和阻止默认行为
- (1)阻止事件冒泡
- (2)阻止默认行为
- 3.事件捕获
- 4.事件处理程序(事件监听器)
- 4.1四种事件处理程序
- 4.2事件绑定(DOM0级别)和事件监听(DOM2)级别本质区别?
- 5.事件对象和事件中的目标对象(注意兼容性)
- 5.1事件对象
- 5.2事件中的目标对象
- 6.事件委托(事件代理)
- 7.事件的兼容性代码
- 7.1事件监听的兼容性写法
- 7.2事件移除的兼容性写法
- 7.3获取事件源的兼容性写法
- 7.4阻止事件冒泡的兼容性写法
- 7.5阻止默认事件的兼容性写法
- 7.6通用事件监听对象
- 8.事件对象中的坐标位置
- 8.1clientX/clientY,x/y
- 8.2screenX/ScreenY
- 8.3pageX/pageY
- 8.4offsetX/offsetY
- 9.自定义事件
1.事件和事件流(事件传播)
1.1什么是事件
事件是可以被 JavaScript 侦测到的行为。 鼠标点击、鼠标移动、鼠标滚动、按下键盘,浏览器窗口大小的改变,网页加载完成,关闭网页等等都会发生事件。
1.2什么是事件流
事件流,描述的是页面中接受事件的顺序。
一个完整的JS事件流是从window开始,最后回到window的一个过程。
事件流被分为三个阶段(1~ 5)捕获过程、(5~ 6)事件触发过程、(6~ 10)冒泡过程。
2.事件冒泡
2.1事件冒泡是什么
事件冒泡总结来说就是: 当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到window。如果它的父级元素也有某个事件函数,当执行完它的事件函数后,也就会执行它父级的事件函数。
下面将举个例子:
代码如下:
```javascript
<div class="parents" id="parents">
<div class="child" id="child"></div>
</div>
...
<script>
let parents = document.getElementById("parents");
let child = document.getElementById("child");
document.onclick = function(e){
alert("你点击了屏幕任何区域");
};
document.body.onclick = function(e){
alert("你点击了body区域");
};
parents.onclick = function (e) {
alert("你点击了parents区域");
};
child.onclick = function (e) {
alert("你点击了child区域");
};
</script>
- 当点击区域①的时候:会依次弹出
你点击了child区域
、你点击了parents区域
、你点击了body区域
、你点击了屏幕任何区域
这四个弹框。 - 当点击区域②的时候:会依次弹出
你点击了parents区域
、你点击了body区域
、你点击了屏幕任何区域
这三个弹框。 - 当点击区域③的时候:会弹出
你点击了body区域
、你点击了屏幕任何区域
这两个弹框。 - 当点击区域④的时候:就只弹出
你点击了屏幕任何区域
这一个弹框。
2.2阻止事件冒泡和阻止默认行为
(1)阻止事件冒泡
e.stopPagepation()
;//ie低版本不行,取消事件的进一步冒泡,但无法阻止同一事件的其它监听函数被调用;e.stopimmediatepropagation()
;//ie低版本不行,既可以取消事件的进一步冒泡,又可以阻止同一事件的其它监听函数被调用;e.cancelBubble=true
;//ie低版本也可以使用
兼容性写法:
let box=document.querySelector(".box");
box.addEventListener("click",function (e){
if (e.stopPropagation){
e.stopPropagation()
}else{
e.cancelBubble=true;
}
})
(2)阻止默认行为
e.preventDefault()
;//ie低版本不行e.returnValue=false
;//火狐,ie9浏览器不支持return false
兼容所有浏览器
兼容性写法:
let box=document.querySelector("#test");
box.addEventListener("click",function (e){
if (e.preventDefault){
e.preventDefault();
}else{
e.returnValue=false;
}
})
3.事件捕获
讲事件捕获之前先了解下addEventListener()方法:
- document.addEventListener() 方法用于向文档添加事件句柄。
- 可以使用 document.removeEventListener() 方法来移除addEventListener() 方法添加的事件句柄
- 使用 element.addEventListener() 方法为指定元素添加事件句柄。
语法如下:
document.addEventListener(event, function, useCapture);
参数 | 描述 |
---|---|
event | 必需。描述事件名称的字符串。 注意: 不要使用 “on” 前缀。例如,使用 “click” 来取代 “onclick”。 提示: 所有 HTML DOM 事件,可以查看我们完整的 HTML DOM Event 对象参考手册。 |
function | 必需。描述了事件触发后执行的函数。当事件触发时,事件对象会作为第一个参数传入函数。事件对象的类型取决于特定的事件。例如, “click” 事件属于 MouseEvent(鼠标事件) 对象。 |
useCapture | 可选。布尔值,指定事件是否在捕获阶段执行。 可能值: true - 事件句柄在捕获阶段执行 false - 默认。事件句柄在冒泡阶段执行 |
从上面的表格中,可以看到参数值useCapture,为true的时候,事件在捕获过程中就会执行。
用代码感受下:
<div id="div1">
<div id="div2">
<div id="div3">
<div id="div4"></div>
</div>
</div>
</div>
<script>
let div1 = document.getElementById("div1");
let div2 = document.getElementById("div2");
let div3 = document.getElementById("div3");
let div4 = document.getElementById("div4");
div1.addEventListener(
"click",
function () {
alert("你点击了div1");
},
false
);
div2.addEventListener(
"click",
function () {
alert("你点击了div2");
},
false
);
div3.addEventListener(
"click",
function () {
alert("你点击了div3");
},
false
);
div4.addEventListener(
"click",
function () {
alert("你点击了div4");
},
false
);
</script>
当点击子元素div2 的时候,会先弹出你点击了div4的弹框
,你点击了div3的弹框
,你点击了div2 的弹框
,再弹出你点击了div1 的弹框
。因为这四个绑定事件的useCapture都为false,表示四个事件都是在冒泡阶段执行-------冒泡阶段由内向外执行。
修改一下代码,来看看什么效果。把div2和div3上绑定事件的useCapture改为true:
<div id="div1">
<div id="div2">
<div id="div3">
<div id="div4"></div>
</div>
</div>
</div>
<script>
let div1 = document.getElementById("div1");
let div2 = document.getElementById("div2");
let div3 = document.getElementById("div3");
let div4 = document.getElementById("div4");
div1.addEventListener(
"click",
function () {
alert("你点击了div1");
},
false
);
div2.addEventListener(
"click",
function () {
alert("你点击了div2");
},
true
);
div3.addEventListener(
"click",
function () {
alert("你点击了div3");
},
true
);
div4.addEventListener(
"click",
function () {
alert("你点击了div4");
},
false
);
</script>
当点击子元素div2 的时候,会先弹出你点击了div2的弹框
,你点击了div3的弹框
,你点击了div4 的弹框
,再弹出你点击了div1 的弹框
。这个应该不难理解。
4.事件处理程序(事件监听器)
4.1四种事件处理程序
- HTML事件处理程序
绑定事件句柄onclick:<div id="div1" onclick="func1"></div>
- DOM0级别:事件绑定(具有跨浏览器的优势,所有浏览器都兼容)
如btn.onclick=func
;btn.onclick=null
;
某个元素对同一类型的事件只能绑定一次,会被覆盖。只支持冒泡,不支持捕获。 - DOM2级别:事件监听(不支持低版本浏览器如IE6,IE7,IE8)
btn.addEventListener("click",func,useCapture)
;
btn.removeEventListener("click",func,useCapture)
;
useCapture默认为false,表示事件不在捕获阶段执行;
某个元素对同一类型的事件可绑定多次,不会被覆盖。 - ie事件模型(基本不用,只支持低版本浏览器)
attachEvent("onclick",func)
,detachEvent("onclick",func)
;
注意是onclick
。只支持冒泡,不支持捕获。
4.2事件绑定(DOM0级别)和事件监听(DOM2)级别本质区别?
事件绑定相当于用一个变量存储了函数的地址,如果再绑定一个事件,相当于变量再指向新函数的地址;事件监听则是用了发布订阅者模式,发布者改变了数据,订阅者订阅事件,一旦事件触发,订阅该事件的函数就会被触发。
5.事件对象和事件中的目标对象(注意兼容性)
5.1事件对象
box.addEventListener("click",function (e){
//e(形参):不支持低版本IE
//window.event:不支持低版本火狐
let event = e || window.event;
...
})
5.2事件中的目标对象
打印事件对象:
box.addEventListener("click", function (e) {
console.log(e);
let event = e || window.event;
});
事件对象中有很多属性。其中有三个是关于事件目标对象的----currentTarget、target、srcElement。
- currentTarget:返回正在执行监听函数的所在的节点(与监听内部的this一致)
- target:返回的是事件实际目标对象(不支持低版本IE)
- srcElement:和target一样返回的是事件实际目标对象(不支持低版本火狐)
我们可以看下面这个例子:
<style>
body,
html {
padding: 0;
margin: 0;
}
.box {
width: 100%;
}
.box > .item {
width: 100%;
height: 60px;
margin-top: 50px;
box-sizing: border-box;
background-color: red;
}
</style>
</head>
<body>
<div class="box">
<div class="item">
<button>1</button>
</div>
<div class="item">
<button>2</button>
</div>
</div>
<script>
let box = document.getElementsByClassName("box")[0];
// currentTarget:返回正在执行监听函数的所在的节点(与监听内部的this一致)
box.addEventListener("click", function (e) {
let event = e || window.event;
console.log(e.currentTarget);
console.log(e.target);
console.log(e.currentTarget === this);
console.log(e.target === this);
});
</script>
</body>
界面如下:
点击box盒子中的非红色区域(所在区域不包含类名为item的div区域),控制台打印结果如下:
点击box盒子中的红色区域但不要点到按钮区域,控制台打印结果如下:
点击box盒子中的按钮区域,控制台打印结果如下:
三种情况进行对比就知道currentTarget、target的区别了。而target和srcElement类似,只是在兼容性上有一定区别。
6.事件委托(事件代理)
<style>
.box {
width: 100%;
}
.box > li {
width: 100%;
height: 60px;
border: 1px solid red;
}
</style>
</head>
<body>
<ul class="box">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<!-- ... -->
<!--li条目由后端发给前端的数目来决定 -->
</ul>
</body>
如下图:
如上,一块空白区域内有很多红色边框的li标签,我们需要点击某个li事件时候触发相应的事件。而在实际开发中,内部li条目的数目是由后端决定,也就是说这块空白区域内部是动态的。我们给li标签绑定事件十分麻烦:需要循环遍历所有的li标签,然后给每个li标签绑定click事件
。这显然是不太好的。我们可以采用下面的方法解决此问题:
<script>
let box = document.getElementsByClassName("box")[0];
box.addEventListener("click", function (e) {
let event = e || window.event;
let target = event.target || event.srcElement;
if (target.nodeName.toUpperCase() == "LI") {
console.log("当前我点击的li条目里面的内容是"+target.innerText);
}
});
</script>
7.事件的兼容性代码
7.1事件监听的兼容性写法
function addEvent(target,eventType,handler){
if(target.addEventListener){
target.addEventListener(eventType,handler,false)
}else{
//ie低版本浏览器里面,this指向window而不是绑定元素target,需要改变this指向
target.attachEvent("on"+eventType,handler.call(target))
}
}
7.2事件移除的兼容性写法
function removeEvent(target,eventType,handler){
if(target.removeEventListener){
target.removeEventListener(eventType,handler,false);
}else{
//ie低版本浏览器里面,this指向window而不是绑定元素target
target.detachEvent("on"+eventType,handler.call(target))
}
}
7.3获取事件源的兼容性写法
let getEvent=function(event){
event = event || window.event;
return event.target || event.srcElement;
};
7.4阻止事件冒泡的兼容性写法
//阻止事件冒泡的兼容性写法
let stopPropagation = function(event){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble = true;
}
};
7.5阻止默认事件的兼容性写法
let preventDefault = function(){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;
}
};
7.6通用事件监听对象
let Event = {
addEvent: function(element,type,callback){
if(element.addEventListener){
element.addEventListener(type,callback,false);
}else if(element.attachEvent){
//ie低版本浏览器里面,this指向window而不是绑定元素target,需要改变this指向
element.attachEvent('on' + type,callback.call(element));
}
},
removeEvent: function(element,type,callback){
if(element.removeEventListener){
element.removeEventListener(type,callback,false);
}else{
//ie低版本浏览器里面,this指向window而不是绑定元素target,需要改变this指向
element.detachEvent('on' + type, callback.call(element));
}
},
getEvent: function(event){
return event || window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
stopPropagation: function(event){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble = true;
}
},
preventDefault: function(event){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;
}
}
}
ps:其实低版本浏览器不支持vue.js里面的很多东西。
8.事件对象中的坐标位置
box.onmousemove=function (e){
e=e || window.event;
//clientX/clientY,x/y是相对于浏览器有效区域的x,y轴的距离
this.innerHTML=`ClientX:${e.clientX};ClientY:${e.clientY};X:${e.x};Y:${e.y}`;
//screenX/ScreenY相对于显示器屏幕的x轴y轴的距离
this.innerHTML=`ScreenX:${e.screenX};ScreenY:${e.screenY};`
//pageX/pageY 相对于页面的x,y轴的距离(考虑滚动条的距离)
this.innerHTML=`PageX:${e.pageX};PageY:${e.pageY};`
// offsetX/offsetY 相对于事件源的x,y轴的距离
this.innerHTML=`OffsetX:${e.offsetX};OffsetY:${e.offsetY};`
}
8.1clientX/clientY,x/y
是相对于浏览器有效区域的x,y轴的距离
浏览器有效区域不包含下图中的紫色区域。
8.2screenX/ScreenY
相对于显示器屏幕的x轴y轴的距离。包含浏览器上面搜索栏那一部分(包含上图中的紫色部分)。
8.3pageX/pageY
相对于页面的x,y轴的距离(要考虑滚动条的距离)。
8.4offsetX/offsetY
相对于事件源的x,y轴的距离。
9.自定义事件
<button>点击</button>
<script>
let oddEv=new CustomEvent("odd",{
bubbles:true,//允许冒泡
cancelable:true,
detail:{
a:1//传自定义参数
}
});
let evenEv=new CustomEvent("even",{
bubbles:true,//允许冒泡
cancelable:true,
detail:{
b:1//传自定义参数
}
});
let count=0;
document.querySelector("button").addEventListener("click",function (){
count++;
if (count%2==1){
this.dispatchEvent(oddEv)
}else{
this.dispatchEvent(evenEv)
}
})
document.querySelector("button").addEventListener("odd", function (e) {
console.log("奇数事件被触发了");
console.log(e.detail); //打印{a: 1}
});
document.querySelector("button").addEventListener("even", function (e) {
console.log("偶数事件被触发了");
console.log(e.detail); //打印{b: 1}
});
</script>
在数据埋点中可能会利用自定义事件来触发一些事件,后续有空我将会讲述一些关于数据埋点方面的知识。