事件高级
事件对象
获取事件对象
是什么
也是个对象,这个对象里有事件触发时的相关信息
例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
如何获取
在事件绑定的回调函数的第一个参数就是事件对象
一般命名为 event 、 ev 、 e
tbody.addEventListener('click', function(e) {
console.log(e)
});
事件对象常用属性
| 语法 | 含义 |
|---|---|
e.type | 获取当前的事件类型 |
e.target | 触发事件的元素 |
e.clientX/e.clientY | 获取光标相对于浏览器可见窗口左上角的位置 |
e.offsetX/e.offsetY | 获取光标相对于当前 DOM 素左上角的位置 |
e.screenX/e.screenY | 获取光标相对于整个电脑屏幕左上角的位置 |
e.key | 用户按下的键盘键的值 |
e.stopPropagation() | 阻止冒泡 |
e.preventDefault() | 阻止默认事件,如不让链接跳转、不让表单提交 |
图例
事件流
事件流与两个阶段说明
-
定义:事件流指的是事件完整执行过程中的流动路径
-
捕获阶段是从父到子
-
冒泡阶段是从子到父
事件捕获和事件冒泡
事件冒泡
-
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
-
事件冒泡是默认存在的。可以没有冒泡阶段。
事件捕获
-
从
DOM的根元素开始去执行对应的事件 (从外到里) -
用户单击某个元素时,由
document先相应,然后做两件事:- 是不是在我的结构范围内触发的
- 我有没有子元素,有的话传下去,子元素重复做1、2步
-
若是用
onclick事件监听,则只有冒泡阶段,没有捕获 -
捕获阶段一定需要!没有捕获阶段没有后续的事件。
注意
js代码只能执行捕获或冒泡其中一个阶段addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)。若传入false代表冒泡阶段触发,默认就是false- 实际开发中很少使用事件捕获,更关注事件冒泡
图例
阻止事件流动
-
冒泡模式默认存在,容易导致事件影响到父级元素。想把事件就限制在当前元素内,就需要阻止事件流动
-
方法
-
阻止冒泡语法
事件对象.stopPropagation() -
鼠标经过事件
-
mouseover和mouseout会有冒泡效果 -
mouseenter和mouseleave没有冒泡效果
-
-
阻止默认行为
e.preventDefault() -
两种注册事件的区别
-
传统
on注册- 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- 直接使用null覆盖偶就可以实现事件的解绑
- 都是冒泡阶段执行的
-
事件监听注册
- 语法:
addEventListener(事件类型, 事件处理函数, 是否使用捕获) - 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- 必须使用
removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段) - 匿名函数无法被解绑
- 语法:
-
-
事件委托
-
概念:事件委托是利用事件流的特征解决一些开发需求的知识技巧
-
原理:利用事件冒泡的特点, 给父元素添加事件,子元素可以触发
-
优点:给父级元素加事件(可以提高性能)
-
语法:
btn.addEventListeren('click' ,function(e){ console.log(e.target) })
事件委托总结
事件委托
- 将事件绑定给已存在的父容器,让子元素进行触发
- 它的复用了事件委托的原理,当子元素触发事件之后,会将事件冒泡给父容器
- 如果想对真正触发事件的元素进行处理,可以使用
e.target获取当前真正触发事件的元素- 如果子元素的操作不一样,需要通过判断,一般我们会对子元素添加一个标识,判断当前触发事件的元素是否有这个标识
总结
习题思考
页面渲染
整体效果如下图所示。
要求把数组的数据输出到页面上;点击录入按钮就把信息新增一行并录入相应的空格;点击删除按钮则删除相应的行。
由于本次案例是基于元素操作实现的,元素操作一定要体现到数据,用户操作是的界面元素,但是本质上是在操作数据。因此以前的 .value 或 appendChlid 已经不再适用。我们要基于数组内的数据进行操作。
数组的数据输出到页面上
-
拿到数据发现是一个数组,因此最开始是遍历数组,拿到每一个元素。
-
声明一个空字符串
htmlStr,用模板字符串拼接结构。 -
输出到
tbody上。
let htmlStr = ''
arr.forEach(function(ele, index) {
// 拼接字符串
htmlStr += `<tr>
<td>${index+1}</td>
<td>${ele.uname}</td>
<td>${ele.age}</td>
<td>${ele.gender}</td>
<td>${ele.salary}</td>
<td>${ele.city}</td>
<td>
<a href="javascript:" class="del" id="${ele.stuId}">删除</a>
</td>
</tr>`;
});
// 把拼接好的字符串输出到tbody中
tbody.innerHTML = htmlStr
本小节犯错:
在最后一步把拼接好的字符串输出到 tbody 中时使用错了方法。
tbody.appendChild(htmlStr)
结果报错。
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
原因是此时 htmlStr 是一个字符串,不是一个节点不能使用 appendChild 方法,而应该使用 innerHTML 方法输出。
添加数据
后期开发是要直接操作数据库内的数据,形成数据驱动视图,不能再用以前的创建 tr ,让 tbody 追加 tr 到末尾这种简单操作页面元素的方式,而是操作数组的形式。
-
获取发布按钮以及表单元素。点击后触发回调函数:
-
创建一个对象,因为数组内的元素都是对象,为该对象赋值,原对象有啥值就赋啥值。
-
数据学号为数组中最后一个元素的学号加1,没有的话赋个初始值;其余数据则用获取好的表单元素的值。
-
用
push方法把数据追加到数组中。 -
重新渲染一次页面,由于与第一步的代码一致,因此可以把前面的方法封装成一个函数,调用。PS:封装成函数后要先自调用一次,不然页面没有数据。
-
add.addEventListener('click', function() {
// 数组内是对象的形式,因此我们声明一个对象,取值
let obj = {
// 学号为数组内最后一个对象的学号加1,如果数组为空则设置一个学号1001
stuId: arr.length > 0 ? arr[arr.length - 1].stuId + 1 : 1001,
uname: unameEle.value,
age: ageEle.value,
gender: genderEle.value,
salary: salaryEle.value,
city: cityEle.value
}
console.log(obj);
arr.push(obj)
init()
)
本小节细节
在对象获取值的地方,要在触发点击事件触发后再获取值,即把获取值的步骤写在点击事件回调函数内,而不是写在函数外。如果写在函数外,则会出现只获取到性别和城市的默认值。
之所以会这样,是因为写在回调函数外部的话,页面刚加载完毕就立刻获取完毕值,而此时用户还未输入值,待输入值按下录入按钮后,系统把一开始录入的初始值打印在页面,而没有默认值的部分自然是空值了。
删除数据
由于是面向数据库(本次是面向数组)操作,因此删除操作要把数据从数组内删除,数组删除操作需要用到代码 splice(索引 , 数量) ,再重新渲染一次页面。于是问题简单明了,在于如何获取到需要删除的值的索引。
以后业务处理的时候需要一个值, 你一般有两种方式获取到这个值。
- 传递参数
- 自己先存储再获取
此时能传递的参数只有索引,而索引值不能用于删除操作,删除操作应该用 id 这种具有唯一性的属性,看回数组内,符合条件的只有 stuId 这个对象属性,因此删除操作可以这么做:
- 在第一步渲染页面的拼接字符串步骤时为每个删除按钮添加一个
id名,因为id名不允许重复,具有唯一性,其id值为自己的stuId。 - 由于删除按钮是未来事件源,因此用到事件委托,为父容器
tbody绑定点击事件,设置事件对象,用e.target来判断触发点击事件的是哪个节点。再用一个变量,接收触发事件的节点的stuId值。 - 循环遍历数组内所有的数据,然后进行判断,如果有和被点击的事件对象
stuId一样的,则取它的i值作为索引,删除,再重新渲染页面。
tbody.addEventListener('click', function(e) {
// 获取到当前触发事件的元素的id号
let id = e.target.id
// 遍历数组的数据,找到符合点击的数据id号
for (let i = 0; i < arr.length; i++) {
if (id == arr[i].stuId) {
// 删除,重新渲染数组
arr.splice(i, 1)
init()
}
}
});
本小节难点
如何获取索引值是一个难点,如果没有现成的值传递,那就自己存储一个 id ,因为 id 是内置属性,方便后续操控事件对象 e 获取,其值为数组对象内具有唯一标识的 stuId ,再用事件对象 e.target.id 获取。用这个值作为识别是哪个元素节点的标识。
然后需要做的是判断,先遍历数组内所有的对象,找到其 stuId 属性与被点击的元素节点的 id 相同的那个对象,此时循环的变量 i 值就是很好的索引值,用于做 splice() 删除操作,最后再渲染页面,就大功告成。