Skip to content

Latest commit

 

History

History
executable file
·
423 lines (325 loc) · 14 KB

第10章 事件.md

File metadata and controls

executable file
·
423 lines (325 loc) · 14 KB

一、概述

事件是一种异步编程的实现方式,本质上是程序各个组成部分之间的通信。DOM支持大量的事件。

事件是可以被 JavaScript 侦测到的行为。网页中的每个DOM元素都可以产生某些可以触发JavaScript函数的事件。简单的说,就是当用户对页面的元素进行操作的时候,页面发生的一些可见或不可见的变化。

传统的页面交互是通过CSS实现的,不过CSS只能实现一些临时的、视觉上的简单交互。但随着互联网技术的发展,只停留在页面上的这些简单交互早已不能满足时代发展的要求了。这个时候JavaScript事件的出现很大程度地提升了界面的可交互性,这种交互不仅是停留在视觉上的,还可以是数据上的交互,可以临时的,通过和服务器端的配合也可以实现永久的页面样式和数据改变,从而更大程度地提升了用户体验。

JavaScript的事件主要包括鼠标事件、键盘事件、表单事件、文档事件及这些事件提供的一系列处理机制,在HTML5规范确立的过程中又出现了触摸事件、拖拽事件、进度事件等一系列,本章的主要内容就是对这些事件进行介绍。

二、事件添加

1. on + 事件类型

var btn = document.querySelector(".btn");
btn.onclick = function() {
    alert("Hello, Li-HONGYAO!");
}

2. 事件监听

事件监听依赖的主要内容是 “监听函数 ”,而监听函数是事件发生时,程序所要执行的函数。它是事件驱动编程模式的主要编程方式。语法形式如下:

el.addEventListener(type, listener[, useCapture]);

监听函数的3个参数具体作用如下:

  • type:事件类型,用字符串表示,不需要 on 前缀,大小写敏感;
  • listener:执行函数,可以是一个函数名,也可以是一个匿名函数,事件发生时,会调用该监听函数;
  • useCapture:表示监听函数是在 “捕获阶段” 触发还是在 “冒泡阶段” 触发,用一个布尔值表示。
    • true :表示在 捕获阶段 触发
    • false: 表示在 冒泡阶段 触发。

监听函数和普通函数在功能上的最大区别就是监听函数不会覆盖同名事件名称的函数,但是,若多个监听函数调用的是同一个函数,那代码也只会执行一次,这点需要注意区分。我们通过两个例子来说明这一点。

1)对同一个按钮元素执行同一个事件的不同监听函数:

<button class="button">BUTTON</button>
var button = document.querySelector(".button");
button.addEventListener("click", function() {
    console.log(1);
}, false);
button.addEventListener("click", function() {
    console.log(2);
}, false);

点击按钮,输出:1,2

2)对同一个按钮元素执行同一个事件的同一个监听函数:

var button = document.querySelector('.button');
button.addEventListener('click', handler, false);
button.addEventListener('click', handler, false);

function handler() {
  console.log('Hello');
}

点击按钮,输出:Hello

和普通函数还有一点区别就是,监听函数是可以移除的。要移除监听函数需要使用 removeEventListener() 方法,移除监听函数的参数必须和添加监听函数完全一致,否则无效。基本表示如下:

var button = document.querySelector('.button');

// -- 添加事件监听
button.addEventListener('click', handler, false);
// -- 移除事件监听
button.removeEventListener('click', handler, false);

function handler() {
  console.log('Hello');
}

另外需要指出的是,在IE10及之前的版本并不支持 addEventListener() 方法,要达到同样的效果需要使用 attachEvent() 方法,事件名称需要加上 on 前缀,而且没有第三个布尔值的参数。还有一点需要注意的是这个方法内部的 this 不是指向实例化的元素,而是指向 window。但IE11终于不再支持这个方法,接受了通用的DOM标准,开始使用 addEventListener 方法。

/**
 * 添加事件
 * @param element  事件对象
 * @param type     事件类型
 * @param callBack 回调函数
 */
function addEvent(element, type, callBack) {
    // 兼容IE10.0以下
    if(element.attachEvent) {
        element.attachEvent('on' + type, callBack);
    }else {
        element.addEventListener(type, callBack, false);
    }
}

三、事件对象 *

事件对象指的是当一个事件产生后,会在事件函数内部生成一个 Event 的对象实例,并且将这个实例作为事件函数的参数(通常命名为event),我们尝试在一个事件中输出这个Event对象:

var button = document.querySelector(".button");
button.addEventListener("click", function(ev) {
  	var ev = ev || window.event;
    var target = ev.srcElement || ev.target;
    console.log(ev); 
}, false);

点击该按钮后在控制台中查看输出的Event对象:

MouseEvent
altKey: false
// 是否冒泡阶段出发
bubbles: true
button: 0
buttons: 0
cancelBubble: false
cancelable: true
// 点击时鼠标的X坐标
clientX: 75
// 点击时鼠标的Y坐标
clientY: 22
composed: true
ctrlKey: false
currentTarget: null
defaultPrevented: false
detail: 1
eventPhase: 0
fromElement: null
isTrusted: true
layerX: 75
layerY: 22
metaKey: false
movementX: 0
movementY: 0
offsetX: 67
offsetY: 11
pageX: 75
pageY: 22
// 事件传播轨迹
path: (5) [button.btn, body, html, document, Window]
relatedTarget: null
returnValue: true
screenX: 75
screenY: 133
shiftKey: false
sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
// 事件源对象
srcElement: button.btn
// 事件触发对象
target: button.btn
timeStamp: 3065.610000000106
toElement: button.btn
// 事件类型
type: "click"
view: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window,}
which: 1
x: 75
y: 22
__proto__: MouseEvent

通过这个输出的事件对象Event,非常详细地反应了在这一次点击过程中的事件和被事件触发的对象之间的关系。包括了一系列重要的信息,如:点击时鼠标的X/Y坐标位置,事件是否冒泡,点击时是否按下了Ctrl/Alt/Shift键,被点击目标的相关信息等内容。但在实际开发过程中,我们只需要部分信息,这个时候就可以通过刚才我们实例化的那个“event”来进行选择性的输出。利用事件对象,我们可以在一次事件中做出更精细的判断,从而帮我们解决一些复杂的逻辑流程,实现更丰富的程序功能。

四、事件传播 *

当一个事件发生以后,它会在不同的DOM节点之间传播,这种传播会使得一个事件在多个节点上触发。事件传播分成三个阶段:

  • 第一阶段:从window对象传导到目标节点,称为 捕获阶段
  • 第二阶段:在目标节点上触发,称为 目标阶段
  • 第三阶段:从目标节点传导回window对象,称为 冒泡阶段

我们我们先来看一个示例,冒泡阶段与捕获阶段。

<div class="a">
    <section class="b">
        <p class="c"></p>
    </section>
</div>
.a {width: 300px;height: 300px;background: pink;}
.b {width: 200px;height: 200px;background: orange;}
.c {width: 100px;height: 100px;background: cyan;}
.a, .b {
    display: flex;
    justify-content: center;
    align-items: center;
}
var tags = [...querySelectorAll(".tag")];
tags.forEach(function(tag, index) {
    tag.addEventListener("click",function(event) {
        // a -> 0
        // b -> 1
        // c -> 2
        alert(index);
    }, true);
});

事件在冒泡阶段触发:2、1、0

事件在捕获阶段触发:0、1、2。

由于事件传播的机制,使得同一个事件会多次触发。

在捕获阶段依次为:window -> document -> html -> body -> div -> section -> p

在冒泡阶段依次为:p -> section -> div -> body -> html -> document -> window

如果你需要阻止事件传播可通过如下方式:

  • event.stopPropagation():阻止事件传播(只会阻止当前监听函数的传播
  • event.stopImmediatePropagation():阻止事件传播(阻止所有监听函数

五、事件代理 *

事件代理又叫做事件委托。利用事件冒泡的原理,让自己所触发的事件交给父元素执行。

**原理:**事件冒泡

优点:

  1. 减少事件注册,节省内存占用。

  2. 当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适

缺陷:

事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。

<div class="wraper">
    <p>A</p>
    <section>B</section>
    <div>C</div>
</div>
var wraper = querySelector(".wraper");
wraper.onclick = function(event) {
  	console.log(this); // wraper
    var tar = event.target
    switch(tar.tagName) {
        case "P": tar.style.color = "red";break;
        case "SECTION": tar.style.color = "green";break;
        case "DIV": tar.style.color = "blue";break;
    }
}

从这个示例可以总结出两个结论:

  • (1)、由于事件的传播机制,父级元素可以为其子级元素绑定事件;
  • (2)、事件中的this仍然是指向最初绑定事件的那个元素,这点需要留心。

1. 鼠标事件 *

事件名称 何时触发
mousedown 鼠标按下
mouseup 鼠标抬起
mousemove 鼠标移动
mouseover 鼠标经过
mouseout 鼠标离开(冒泡)
mouseleave 鼠标离开
mouseenter 鼠标进入
click 单击
dblclick 双击
contextmenu 点击鼠标右键

2. 焦点事件 *

事件名称 何时触发
focus 元素获得焦点(不会冒泡) *
blur 元素失去焦点(不会冒泡)*

3. 网络事件(H5) *

事件名称 何时触发
online 浏览器已获得网络访问。
offline 浏览器已失去网络访问。
window.ononline = function() {
    console.log("网络已连接");
}
window.onoffline = function() {
    console.log("网络已断开");
}

4. 表单事件 *

事件名称 何时触发
reset 点击重置按钮时
submit 点击提交按钮
input 输入框值变化(每输入一个字符就会触发一次)
change 表单元素值变化时触发

5. 键盘事件

事件名称 何时触发
keydown 按下任意按键
keypress 除 Shift, Fn, CapsLock 外任意键被按住. (连续触发)
keyup 释放任意按键

提示:键盘事件需添加至window对象上。

6. 窗口事件 *

事件名称 何时触发
resize 调整窗口
scroll 窗口滚动
beforeunload 页面刷新或关闭时触发

7. 拖拽事件(H5)

事件名称 事件名称
dragstart 用户开始拖动HTML元素或选中的文本
drag 正在拖动元素或文本选区(在此过程中持续触发,每350ms触发一次)
dragend 拖放操作结束 (松开鼠标按钮或按下Esc键)
dragenter 被拖动的元素或文本选区移入有效释放目标区
dragover 被拖动的元素或文本选区正在有效释放目标上被拖动 (在此过程中持续触发,每350ms触发一次)
dragleave 被拖动的元素或文本选区移出有效释放目标区
drop 元素在有效释放目标区上释放

8. 触摸事件(H5)

提示:[参考MDN触摸事件]([https://developer.mozilla.org/zh-CN/docs/Web/Events#%E8%A7%A6%E6%91%B8%E4%BA%8B%E4%BB%B6](https://developer.mozilla.org/zh-CN/docs/Web/Events#触摸事件)

七、拓展

语法01

window.scrollTo(x-coord,y-coord)
  • x-coord 是文档中的横轴坐标。
  • y-coord 是文档中的纵轴坐标。

语法02

window.scrollTo(options)  
  • top 等同于 y-coord
  • left 等同于 x-coord
  • behavior 类型String,表示滚动行为
    • smooth(平滑滚动)
    • instant(瞬间滚动),
    • 默认值 auto,实测效果等同于instant
window.scrollTo({
    left: 0,
    top : 0,
    behavior: "smooth"
});

提示:如果某个元素滚动到某个位置,也可以用以上方法:

document.querySelector('.className').scrollTo()

2. 阻止事件冒泡

// IE
ev.cancelBubble = true;
// 非IE
ev.stopPropagation();

3. 阻止默认事件

// 1.
return false;
// 2.
ev.preventDefault()

4. document load vs document ready

document.onload 是在结构/样式/外部js以及图片加载完才执行js。 document.ready 是dom树创建完成就执行的方法,原生种没有这个方法,jQuery中有 $().ready(fn)