JavaScript 高级事件


1.注册事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 传统方式注册事件
// 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数
btns[0].onclick = function() {
alert('hi');
}
btns[0].onclick = function() {
alert('hao a u');
}

// 2. 事件侦听注册事件 addEventListener 推荐使用
// (1) 里面的事件类型是字符串 必定加引号 而且不带on
// (2) 同一个元素 同一个事件可以添加多个侦听器(事件处理程序)按注册顺序依次执行
btns[1].addEventListener('click', function() {
alert(22);
})
btns[1].addEventListener('click', function() {
alert(33);
})

// 3. attachEvent ie9以前的版本支持
btns[2].attachEvent('onclick', function() {
alert(11);
})

2.删除事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 传统方式删除事件
divs[0].onclick = function () {
alert(11);
divs[0].onclick = null;
}

// 2. removeEventListener 删除事件
divs[1].addEventListener('click', fn) // 里面的fn 不需要调用加小括号
function fn() {
alert(22);
divs[1].removeEventListener('click', fn);
}

// 3. detachEvent
divs[2].attachEvent('onclick', fn1);

function fn1() {
alert(33);
divs[2].detachEvent('onclick', fn1);
}

3.DOM事件流

事件流描述的是从页面中接收事件的顺序。 事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。 比如我们给一个div 注册了点击事件:

DOM 事件流分为3个阶段:

  1. 捕获阶段:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。
  2. 当前目标阶段
  3. 冒泡阶段:IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点的过程。

注意

  1. JS 代码中只能执行捕获或者冒泡其中的一个阶段。
  2. onclick 和 attachEvent 只能得到冒泡阶段。
  3. addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕 获阶段调用事件处理程序;如果是 false(不写默认就是false),表示在事件冒泡阶段调用事件处理 程序。
  4. 实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
  5. 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
  6. 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件。

4.事件对象

(1)定义

  • event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。

  • 事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象

event,它有很多属性和方法。

  • 事件对象我们可以自己命名 比如 event 、 evt、 e
1
2
3
4
5
6
7
8
9
// 这个 event 是个形参,系统帮我们设定为事件对象,不需要传递实参过去。
// 当我们注册事件时, event 对象就会被系统自动创建,并依次传递给事件监听器(事件处理函数)。
eventTarget.onclick = function(event) {
console.log(event);
}
eventTarget.addEventListener('click', function(event) {
console.log(event);
}
// 事件对象也有兼容性问题 ie678 通过 window.event 兼容性的写法 e = e || window.event;

(2)e.target 和 this 的区别

  • this 是事件绑定的元素, 这个函数的调用者(绑定这个事件的元素) ;有一个跟 this 有个非常相似的属性 currentTarget 但是ie678不认识
  • e.target 是事件触发的元素。

(3)阻止默认行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = document.querySelector('a');
a.addEventListener('click', function(e) {
e.preventDefault(); // dom 标准写法
})

// 传统的注册方式
a.onclick = function(e) {
// 普通浏览器 e.preventDefault(); 方法
// e.preventDefault();
// 低版本浏览器 ie678 returnValue 属性
// e.returnValue;
// 我们可以利用return false 也能阻止默认行为 没有兼容性问题 特点: return 后面的代码不执行了, 而且只限于传统的注册方式
return false;
alert(11); // 不执行
}

5.阻止事件冒泡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 常见事件对象的属性和方法
// 阻止冒泡 dom 推荐的标准 stopPropagation()
var son = document.querySelector('.son');
son.addEventListener('click', function (e) {
alert('son');
e.stopPropagation(); // stop 停止 Propagation 传播
// e.cancelBubble = true; // 非标准 IE 6-8 利用事件对象 cancelBubble 属性
}, false); // 若第三个参数为false或省略表示冒泡阶段,若为true,则为捕获阶段
var father = document.querySelector('.father');
father.addEventListener('click', function () {
alert('father');
}, false);
document.addEventListener('click', function () {
alert('document');
})

6.事件委托(代理、委派)

事件冒泡本身的特性,会带来的坏处,也会带来的好处,需要我们灵活掌握。程序中也有如此场景:

1
2
3
4
5
6
7
<ul> 
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
</ul>

点击每个 li 都会弹出对话框,以前需要给每个 li 注册事件,是非常辛苦的,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间。

事件委托也称为事件代理, 在 jQuery 里面称为事件委派。

事件委托的原理:不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。(面试考点)

以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。

1
2
3
4
5
6
7
// 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
// alert('知否知否,点我应有弹框在手!');
// e.target 这个可以得到我们点击的对象
e.target.style.backgroundColor = 'pink';
})

事件委托的作用:只操作了一次 DOM ,提高了程序的性能。

7.鼠标事件:禁止右键菜单和选择

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
我是一段不愿意分享的文字
<script>
// 1. contextmenu 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2. 禁止选中文字 selectstart 开始选中
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
</script>
</body>

8.鼠标事件对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 鼠标事件对象 MouseEvent
document.addEventListener('click', function(e) {
// 1. client 鼠标在可视区的x和y坐标
console.log(e.clientX);
console.log(e.clientY);
console.log('---------------------');
// 2. page 鼠标在页面文档的x和y坐标
console.log(e.pageX);
console.log(e.pageY);
console.log('---------------------');
// 3. screen 鼠标在电脑屏幕的x和y坐标
console.log(e.screenX);
console.log(e.screenY);
})

案例:跟随鼠标的天使

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
img {
position: absolute;
top: 2px;
}
</style>
</head>

<body>
<img src="images/angel.gif" alt="">
<script>
var pic = document.querySelector('img');
document.addEventListener('mousemove', function(e) {
// 1. mousemove只要我们鼠标移动1px 就会触发这个事件
// console.log(1);
// 2.核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, 把这个x和y坐标做为图片的top和left 值就可以移动图片
var x = e.pageX;
var y = e.pageY;
console.log('x坐标是' + x, 'y坐标是' + y);
//3 . 千万不要忘记给left 和top 添加px 单位
pic.style.left = x - 50 + 'px';
pic.style.top = y - 40 + 'px';
});
</script>
</body>

9.键盘事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. keyup 按键弹起的时候触发
document.onkeyup = function () {
console.log('我弹起了');
}
// 如果使用addEventListener 不需要加 on
document.addEventListener('keyup', function () {
console.log('我弹起了');
})
//3. keypress 按键按下的时候触发 不能识别功能键 比如 ctrl shift 左右箭头
// Keypress 不识别功能键,但是keyCode属性能区分大小写,返回不同的ASCII值
document.addEventListener('keypress', function () {
console.log('我按下了press');
})
//2. keydown 按键按下的时候触发 能识别功能键 比如 ctrl shift 左右箭头
document.addEventListener('keydown', function () {
console.log('我按下了down');
})
// 4. 三个事件的执行顺序 keydown -- keypress -- keyup

keycode属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 键盘事件对象中的keyCode属性可以得到相应键的ASCII码值
// 1. 我们的keyup 和keydown事件不区分字母大小写 a 和 A 得到的都是65
// 2. 我们的keypress 事件 区分字母大小写 a 97 和 A 得到的是65
document.addEventListener('keyup', function(e) {
// console.log(e);
console.log('up:' + e.keyCode);
// 我们可以利用keycode返回的ASCII码值来判断用户按下了那个键
if (e.keyCode === 65) {
alert('您按下的a键');
} else {
alert('您没有按下a键')
}
})
document.addEventListener('keypress', function(e) {
// console.log(e);
console.log('press:' + e.keyCode);
})

案例:模拟京东按键聚焦到输入框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<input type="text">
<script>
// 核心思路: 检测用户是否按下了s 键,如果按下s 键,就把光标定位到搜索框里面
// 使用键盘事件对象里面的keyCode 判断用户按下的是否是s键
// 搜索框获得焦点: 使用 js 里面的 focus() 方法
var search = document.querySelector('input');
document.addEventListener('keyup', function(e) {
// console.log(e.keyCode);
if (e.keyCode === 83) {
search.focus();
}
})
</script>
</body>

案例:模拟输入框文字放大效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

.search {
position: relative;
width: 178px;
margin: 100px;
}

.con {
/* display: none; */
position: absolute;
top: -40px;
width: 171px;
border: 1px solid rgba(0, 0, 0, .2);
box-shadow: 0 2px 4px rgba(0, 0, 0, .2);
padding: 5px 0;
font-size: 18px;
line-height: 20px;
color: #333;
}

.con::before {
content: '';
width: 0;
height: 0;
position: absolute;
top: 28px;
left: 18px;
border: 8px solid #000;
border-style: solid dashed dashed;
border-color: #fff transparent transparent;
}
</style>
</head>

<body>
<div class="search">
<div class="con">123</div>
<input type="text" placeholder="请输入您的快递单号" class="jd">
</div>
<script>
// 快递单号输入内容时, 上面的大号字体盒子(con)显示(这里面的字号更大)
// 表单检测用户输入: 给表单添加键盘事件
// 同时把快递单号里面的值(value)获取过来赋值给 con盒子(innerText)做为内容
// 如果快递单号里面内容为空,则隐藏大号字体盒子(con)盒子
var con = document.querySelector('.con');
var jd_input = document.querySelector('.jd');
jd_input.addEventListener('keyup', function () {
// console.log('输入内容啦');
if (this.value == '') {
con.style.display = 'none';
} else {
con.style.display = 'block';
con.innerText = this.value;
}
})
// 当我们失去焦点,就隐藏这个con盒子
jd_input.addEventListener('blur', function () {
con.style.display = 'none';
})
// 当我们获得焦点,就显示这个con盒子
jd_input.addEventListener('focus', function () {
if (this.value !== '') {
con.style.display = 'block';
}
})
</script>
</body>