【JS】事件详解(包含绑定、传播、委托和阻止事件)

一、事件绑定

1.1、HTML的 on- 属性

  • 注意:HTML里面的事件属性值是将会执行的代码,而不是一个函数
<!-- on 加上事件名,比如 onload 就是 on + load -->
<body onload="doSomething()">
<!-- on 加上事件名,比如 onclick 就是 on + click -->
<div onclick="console.log('触发事件')">

1.2、元素的 .on 属性

  • 注意:这种方法与 HTML 的on-属性的差异是,它的值是函数名
// 获取页面上的 div 标签
var oDiv = document.querySelector('div');

oDiv.onclick = function(){
	//谁来触发事件 => oDiv => 这个事件的事件源就是oDiv
	//触发什么事件 => onclick => 这个事件类型就是cLick
	//触发之后做什么 => function(){} => 这个事件的处理函数
	console.log('你点击了div');	// 每当点击一次,就会执行一次事件处理函数 
}
- 当你点击的时候,只会执行第二个,第一个就没有了
  • 我们还有一种事件监听的方式给元素绑定事件
  • 使用addEventListener的方式添加
    • 这个方法不兼容,在IE里面要使用attachEvent

1.3、事件监听

addEventListener(绑定事件)
  • addEventListener:非IE 7、8使用
/*
	target: 元素
	type:事件类型,大小写敏感
	listener:监听函数,事件发生时,会调用该监听函数。
	useCapture:布尔值,默认false(冒泡触发),true(设置捕获触发)
*/
target.addEventListener(type, listener[, useCapture]);
  • 示例:
oDiv.addEventListener('click',function(){
	console.log('我是第一个事件');
},false)
oDiv.addEventListenner('click',function(){
	console.log('我是第二个事件');
},false)
- 当你点击 div 的时候,两个函数都会执行,标签会按照你注册的顺序执行
- 先打印`我是第一个事件`再打印`我是第二个事件`
- 注意:事件的类型不要写on,点击事件就是click,不是onclick
  • attchEvent:IE 7 8下使用
  • 语法:元素.attachEvent('事件类型',事件处理函数);
oDiv.attachEvent('onclick',function(){
	console.log("我是第一个事件");
})
oDiv.attackEvent('onclick',function(){
	console.log('我是第二个事件');
})
- 当你点击div 的时候,两个函数都会执行,并且会按照你注册的顺序倒叙执行
- 先打印 我是第二个事件 再打印 我是第一个事件
- 注意:事件类型要写on,点击事件就写onclick
区别attachEventaddEventListener
on要写on不用写on
参数个数2个参数一般是3个常用参数
执行顺序顺序注册,倒叙执行顺序注册,顺序执行
兼容性IE 7 8浏览器非IE 7 8的浏览器
removeEventListener(移除事件)
  • removeEventListener()方法用来移除addEventListener()方法添加的事件监听函数。该方法没有返回值。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
  • removeEventListener()方法的参数,与addEventListener()方法完全一致
// 错误案例:监听函数不是同一个匿名函数,导致失效
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);

// 错误案例:因为第三个参数不一样,导致失效
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
dispatchEvent(触发事件)
  • dispatchEvent()方法在当前节点上触发指定事件,从而触发监听函数的执行。
  • 该方法返回一个布尔值,只要有一个监听函数调用了preventDefault(),则返回值为false,否则为true
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);

if (!para.dispatchEvent(event)) {
  console.log('事件取消');
} else {
  console.log('事件未取消');
}
  • 案例:
const onMorePath = useCallback((e) => {
      e.stopPropagation();
      e.preventDefault();
      const event = new MouseEvent('contextmenu', {
        bubbles: true,
        cancelable: false,
        view: window,
        button: 2,
        buttons: 0,
        clientX: e.currentTarget.getBoundingClientRect().x + 24,
        clientY: e.currentTarget.getBoundingClientRect().y + 24,
      });
      e.currentTarget.dispatchEvent(event);
    }, []);

二、事件传播

2.1、事件执行机制

  • 什么是事件的执行机制呢?
    • 思考一个问题:
    • 当一个大盒子嵌套一个小盒子的时候,并且两个盒子都有点击事件
    • 你点击里面的小盒子,外面的大盒子上的点击事件要不要执行

在这里插入图片描述

  • 就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
  • 这个是既定事实,那么两个盒子的点击事件都会触发
  • 这个就叫做 事件的传播
var phases = {
  1: 'capture', // 捕获
  2: 'target',	// 目标
  3: 'bubble'	// 冒泡
};

// 假设 div 为粉色大盒子
var div = document.querySelector('div');
// 假设 p 为粉色大盒子里面的红色盒子
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 + "'");
}
// 点击 p 标签红色盒子以后的结果
// Tag: 'DIV'. EventPhase: 'capture' 
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
  • 注意:浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是<div>节点里面的<p>节点)。
  • 所以:<p>节点的捕获阶段和冒泡阶段,都会显示为target阶段,而<p>节点有两个监听函数(一个捕获、一个冒泡),所以<p>会在target阶段有 2 次输出,而<div>节点的捕获阶段和冒泡阶段各1次
捕获阶段:事件从`<div>``<p>`传播时,触发`<div>`的click事件;
目标阶段:事件从`<div>`到达`<p>`时,触发`<p>`的click事件;
冒泡阶段:事件从`<p>`传回`<div>`时,再次触发`<div>`的click事件。
  • 上例的事件传播顺序为:
在捕获阶段依次为: window、document、html、body、div、p,
在冒泡阶段依次为: p、div、body、html、document、window。

2.2、冒泡、捕获、目标

一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  1. 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
  2. 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
  3. 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
  • 目标:你是点击在哪个元素身上了,那么这个事件的 目标 就是什么

  • 冒泡:就是从事件 目标 的事件处理函数开始,依次向外,直到 window 的事件处理函数触发,也就是从下向上的执行事件处理函数

  • 捕获:就是从 window 的事件处理函数开始,依次向内,只要事件 目标 的事件处理函数执行,也就是从上向下的执行事件处理函数

三、事件委托

  • 就是把我要做的事情委托给别人来做
  • 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
  • 所以我们就可以把子元素的事件委托给父元素来做

3.1、为什么要用

  • 点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以出发父元素的点击事件
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script>
  	var oUl = document.querySelector('ul');
	oUL.addEventListener('click',function(e){
		// 当你点击 ul 或 li 的时候都会触发
		console.log('我是 ul 的点击事件,我被触发了');
	})
  </script>
</body>
  • 为什么要用事件委托
1. 我页面上本身没有<li>, 我通过代码添加了一些<li>,添加进来的<li>是没有点击事件的
2. 我每次动态的操作完<li>以后都要从新给<li>绑定一次点击事件,比较麻烦
3. 这个时候只要委托给<ul>就可以了
4. 因为新加进来的<li>也是<ul>的子元素,点击的时候也可以触发<ul>的点击事件
  • 事件委托的书写
元素的事件只能委托给 结构父级 或者 再结构父级 的 同样 的事件上
- <li>的点击事件,就不能委托给<ul>的鼠标 移入事件
- <li>的点击事件,只能委托给<ul>或者 在高父级的点击事件上

3.2、target 实现

  • target 这个属性是事件对象里面的属性,表示你点击的目标
  • 当你触发点击事件的时候,你点击在哪个元素上,target 就是哪个元素
  • 这个 target 也不兼容,在 IE 下要使用 srcElement
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script>
  	var oUl = docuemnt.querySelector('ul')
    
    oUl.addEventListener('click', function (e) {
      e = e || window.event
      var target = e.target || e.srcElement
     
      // 判断你点击的是 li
      if (target.nodeName.toUpperCase === 'LI') {
      	// 确定点击的是 li
        // 因为当你点击在 ul 上面的时候,nodeName 应该是 'UL'
        // 我们就可以把 li 要做的事情委托给 ul 来做
        console.log('我是 li,我被点击了')
      }
    })
  </script>
</body>

四、阻止事件

4.1、阻止默认行为(preventDefault)

  • 默认行为,就是不用我们注册,它自己就存在的事情

    • 比如我们点击鼠标右键的时候,会自动弹出一个菜单
    • 比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面
    • 。。。
  • 这些不需要我们注册就能实现的事情,我们叫做 默认事件

  • 有的时候,我们不希望浏览器执行默认事件

    • 比如我给 a 标签绑定了一个点击事件,我点击你的时候希望你能告诉我你的地址是什么
    • 而不是直接跳转链接
    • 那么我们就要把 a 标签原先的默认事件阻止,不让他执行默认事件
  • 我们有两个方法来阻止默认事件

    • e.preventDefault() : 非 IE 使用
    • e.returnValue = false :IE 使用
  • 我们阻止默认事件的时候也要写一个兼容的写法

<a href="https://www.baidu.com">点击我试试</a>
<script>
	var oA = document.querySelector('a')
  
  a.addEventListener('click', function (e) {
    e = e || window.event
    
    console.log(this.href)
    
    e.preventDefault ? e.preventDefault() : e.returnValue = false
  })
</script>
- 这样写完以后,你点击 a 标签的时候,就不会跳转链接了
- 而是会在控制台打印出 a 标签的 href 属性的值

4.2、阻止事件传播(stopPropagation)

  • 如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, true);

// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, false);
  • stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数
p.addEventListener('click', function (event) {
  event.stopPropagation();
  console.log(1);	// 能正常打印
});
// 另一个click事件的监听函数也会触发
p.addEventListener('click', function(event) {
  // 会触发
  console.log(2);
});

4.3、阻止事件触发(stopImmediatePropagation)

  • 如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法

  • stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发

p.addEventListener('click', function (event) {
  event.stopImmediatePropagation();
  console.log(1);	// 能正常打印
});
// 另一个click事件的监听函数不会触发,因为在上一个监听函数中阻止了
p.addEventListener('click', function(event) {
  // 不会被触发
  console.log(2);
});

五、事件对象(Event)

5.1、基本介绍

  • 什么是事件对象?
  • 就是当你触发一个事件以后,对该事件的一些描述信息
  • 例如:
    • 你触发一个点击事件的时候,你点在哪个位置了,坐标是多少
    • 你触发了一个键盘事件的时候,你按的是哪个按钮
    • 。。。。。
  • 每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做事件对象
  • 浏览器给了我们应该黑盒子,叫做window.event,就是对事件信息的所有描述
    • 比如点击事件
    • 你点在了0,0位置,那么你得到的这个事件对象里面对应的就会有一个点位的属性
    • 你点在了10,10位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性
    • 。。。
oDiv.onclick = function(){
	console.log(window.event.X轴坐标点信息);
	console.log(window.enent.Y轴坐标点信息);
}
  • 这玩意很好用,但是一般来说,好用的东西就会有兼容性问题
  • IE低版本里面这个东西好用,但是在高版本IEChrome里面不好使了
  • 我们就得用另一种方式来获取事件对象
  • 在每一个事件处理函数的形参位置,默认第一个就是事件对象
oDiv.onclick = function (e){
	// e 就是和IE的window.event 一样的东西
	console.log(e.X轴的坐标点信息);
	console.log(e.Y轴坐标点信息);
}
  • 综上所述,我们以后每一个事件里面,想要获取事件对象的时候,都用兼容写法
oDiv.onclick = function(e){
	e = e || window.event;
	console.log(e.X轴坐标点的信息);
	console.log(e.Y轴坐标点的信息);
}

5.2、坐标获取(示例)

  • 刚才既然说了,可以获取到坐标点,那么接下来我们就学习一下怎么获取坐标点
  • 我们的每一个点击事件的坐标点都不是一对,因为要有一个相对的坐标系
  • 例如:
    • 相对事件源(你点击的元素)
    • 相对页面
    • 相对浏览器窗口
    • 。。。。。
  • 因为都不一样,所以我们获取的事件对象里面的属性也不一样
相对于你点击的元素
  • offsetXoffsetY
  • 是相对于你点击的元素的边框内侧开始计算
<style>
  * {
    margin: 0;
    padding: 0;
  }

  div {
    width: 300px;
    height: 300px;
    padding: 20px;
    border: 10px solid #333;
    margin: 20px 0 0 30px;
  }
</style>
<body>
  <div></div>

  <script>
    var oDiv = document.querySelector('div')
	//注册点击事件
	oDiv.onclick = function(e){
		//事件对象兼容写法
		e = e || window.event;
		console.log(e.offsetX);
		console.log(e.offsetY);
	}
  </script>
</body>

在这里插入图片描述

相对于浏览器可视窗口你点击的坐标点
  • clientXclientY
  • 是相当于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据可视窗口来计算坐标
<style>
  * {
    margin: 0;
    padding: 0;
  }

  body {
    width: 2000px;
    height: 2000px;
  }

  div {
    width: 300px;
    height: 300px;
    padding: 20px;
    border: 10px solid #333;
    margin: 20px 0 0 30px;
  }
</style>
<body>
  <div></div>

  <script>
    var oDiv = document.querySelector('div');

	//注册点击事件
	oDiv.onclick = function(e){
		//事件对象兼容写法
		e = e || window.event;
		console.log(e.clientX);
		console.log(e.clientY);
	}
  </script>
</body>
相当于页面你点击的坐标点
  • pageXpageY
  • 是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点
<style>
  * {
    margin: 0;
    padding: 0;
  }

  body {
    width: 2000px;
    height: 2000px;
  }

  div {
    width: 300px;
    height: 300px;
    padding: 20px;
    border: 10px solid #333;
    margin: 20px 0 0 30px;
  }
</style>
<body>
  <div></div>

  <script>
    var oDiv = document.querySelector('div');
    //注册点击事件
    oDiv.onclick = function(e){
    //事件对象兼容写法
    e = e || window.event;
    console.log(e.pageX);
    console.log(e.pageY);
    }
  </script>
</body>
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值