什么是事件?
事件: 事件是浏览器赋予元素的默认行为, 也可以理解为天生具备的,不论我们是否是否为其绑定方法,当某些行为触发的时候,相关的事件都会被触发执行!如果方法不赋值,则触发事件时则什么都不做!
- 鼠标事件
- click :点击事件(PC); 单击事件(移动端,300ms内无第二次点击操作,则是单击,所以移动端click事件有300ms延迟)
- dblclick: 在元素上双击鼠标按钮。
- contextmenu:鼠标点击右键触发(在右键菜单显示前触发)。
- mousedown:鼠标按下。
- mouseup: 鼠标释放(抬起)。
- mousemove:鼠标移动。结合mousedown和mouseup实现鼠标拖拽功能实现
- mouseenter:鼠标进入。
- mouseleave:鼠标移出(不冒泡)。
- mouseover: 鼠标滑入。
- mouseout:鼠标滑出。
- wheel:鼠标滚轮向任意方向滚动。
- …
- 键盘事件:
- keydown:按下任意按键。
- keypress:除 Shift、Fn、CapsLock 外的任意键被按住。(连续触发。)
- keyup: 释放任意按键。
- 触摸事件(手指事件):
- 单手指事件
- touchend:手指松开
- touchmove:手指移动
- touchstart:手指按下
- …
- 多手指事件:Gesture Event
- 单手指事件
- 网络事件
- online获取网络
- offline 失去网络
- 表单事件:
- focus:获取焦点
- blur:失去焦点
- reset:点击重置按钮时 (前提:表单元素都包含在form中,并且点击的是按钮reset)
- submit:点击提交按钮(前提:表单元素都包含在form中,并且点击的是按钮submit)
- select:下拉框内容选中。
- change:当用户改变、和 元素的值并提交这个更改时触发
- input:移动端常用,监控文本中的内容随着输入的改变而触发
- …
- 资源事件:
- load:加载成功 (
window.onload / img.onload
) - error:加载失败
- abort:加载终止
- beforeUnload:资源卸载前触发(
window.onbeforeunload
页面关闭前触发) - unload: 文档或者资源正在被卸载
- …
- load:加载成功 (
- CSS3动画事件
- transitionstart: transition开始
- transitionend: transition完成
- transitionrun: transition正在运行
- transitioncancel: transition被终止
- animationstart:CSS 动画开始时触发
- animationend:CSS 动画完成时触发
- animationiteration: CSS 动画完成后重新开始时触发
- 视图事件
- resize:元素(浏览器)大小改变
- scroll :滚动条滚动
- 剪切板事件
- cut:剪贴并且复制选中的文本内容到剪贴板。
- copy:把选中的文本内容复制到剪贴板。
- paste:从剪贴板复制的文本内容被粘贴。
- 拖放事件
- drag 350ms 触发一次)
- dragend:拖放操作结束。(松开鼠标按钮或按下 Esc 键)
- dragenter:被拖动的元素或文本选区移入
- dragstart:开始拖动元素或选中的文本
- dragleave:被拖动的元素或文本移出
- dragover:被拖动的元素或文本正在被拖动 (在此过程中持续触发,每350ms触发一次)
- drop:释放元素
- …
什么是事件绑定?
事件绑定:给元素的某个事件行为绑定方法,这样事件行为触发的时候,对应绑定的方法会执行,完成一些需要完成的功能
DOM0级事件绑定
- 语法:[元素].on[事件] = [函数] =>
document.body.onclick = function(){};
- 移除绑定:赋值为 null 或者其他非函数值即可 =>
document.body.onclick = null;
- 原理:
- 每一个DOM元素对象的私有属性上都有很多类似于“onxxx”的属性,DOM0事件绑定的原理就是给当前元素对象的 “onxxx” 私有属性赋值一个函数 ,当事件行为触发,浏览器就会把绑定的方法执行,
- 如果没有对应的私有属性值(ontransitionend/onDOMContentLoad…),则无法基于这种办法实现DOM0事件绑定
- 只能给当前元素的某个事件行为绑定一个方法,多次绑定,最后一次操作会覆盖以前的。
- 好处是执行效率快,使用方便
DOM2级事件绑定
- 语法:
- 【标准浏览器】:[元素].addEventListener([事件],[方法],[捕获/冒泡]) => document.body.addEventListener(“click”, fn1, false);
- 【IE6~8】:元素.attchEvent(“onxxx”,function(){…});
- 移除:[元素].removeEventListener([事件],[方法],[]捕获/冒泡) => document.body.removeEventListener(“click”, fn1, false);
- 原理:
- 每一个DOM元素都会基于
__proto__
属性找到EventTarget.prototype
上的addEventListener / removeEventListener
等方法基于这些方法实现事件的绑定和移除; - DOM2级事件绑定底层原理是基于浏览器内置的事件池机制来完成事件的监听和方法绑定的
- DOM2级事件绑定,绑定的方法一般不是匿名函数 ,主要目的是方便移除事件绑定的时候用
- 只要是浏览器提供的事件行为,都可以基于DOM2事件绑定机制完成事件的绑定和移除(例如DOMContentLoaded)
- 可以给当前元素的某个事件行为绑定多个"不同"的方法,事件行为触发,会按照绑定的顺序把方法依次执行
- 每一个DOM元素都会基于
- 真实开发过程中。DOM0和DOM2可以共存(不会冲突),而且一般以用DOM2偏多一些,因为其机制相对比较完善;而且类似于JQ这种类库或者一些常用的插件,基本上也是基于DOM2事件绑定封装事件处理方法的:
- JQ中的事件绑定方法:on/off 这是最底层的,是基于DOM2完成的(bind、unbind 、delegate、click、mouseover…都是最后基于on/off完成的)
经典面试题: window.onload
和 document.ready[/ JQ:$(document).ready() ]
的区别是什么?
window.onload
是等待所有资源都加载完成才会触发事件,$(document).ready()
用的是DOMContentLoad
事件,事件本身是DOM结构加载完成就触发执行,$(document).ready()
优先于window.onload
触发window.onload
是基于DOM0事件绑定的,所以页面中只能用一次;而JQ中的事件绑定都是基于DOM2完成的,所以可以在相同页面绑定多个不同的方法,JQ开发中,一般把编写的模块代码放到$(function){})中,形成闭包还能保证DON结构加载完成才执行- 不论哪一种办法,都是为了保证DOM结构加载完成在执行,这样在方法中肯定能获取到DOM元素。防止把JS放到DOM之前加载,导致元素无法获取的问题(引申:JS/CSS/IMG/结构加载的顺序和机制 =>浏览器渲染机制)
事件对象
给当前元素的某个事件行为绑定方法,当事件行为触发,不仅会把的绑定的方法执行,而且还会给方法默认传递一个实参,而这个实参就是事件对象;
事件对象:存储当前事件操作及触发的相关信息(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)。
输出结果:
事件对象
- 鼠标事件对象MouseEvent:=>
document.body.onclick = function(ev){console.log(ev)}
- clientX / clientY: 鼠标触发点距离当前窗口的X/Y坐标轴
- pageX / pageY:鼠标触发点距离BODY的X/Y坐标轴
- offsetX / offsetY:
- screenX / screenY:
- type:事件类型
- srcElement / target:获取当前事件源(当前操作的元素)
- path:传播路径
- ev.preventDefault() / ev.returnValue=false:阻止默认行为
- ev.stopPropagation() / ev.cancelBubble = true:阻止冒泡传播
- 键盘事件对象KeyboardEvent:=>
document.onkeydown = function(ev){console.log(ev)}
- key / code:按键名字
- which / keyCode:获取键盘的键盘码
- 方向键:“左37、上38、右39、下40”
- Space:32
- BackSpace:8
- Del:46 =>Mac没有BackSpace ,Del是8
- Enter:13
- Shift:16
- Ctrl:17
- Alt:18
- altKey:Alt键是 (true) 否 (false) 被按下。(组合按键)
- ctrlKey:Ctrl键是 (true) 否 (false) 被按下。(组合按键)
- shiftKey:Shift键是 (true) 否 (false) 被按下。(组合按键)
- 手指事件对象TouchEvent(移动端):=>
document.body.ontouchstart = function(ev){console.log(ev)}
- touches / changeTouches 都是用来记录手指信息的,常用changeTouches
- 手指按下、移动或者离开屏幕,changeTouches 都存储了对应的手指信息,即使手指离开屏幕,存储的也是最后一次手指在屏幕的信息;而 touches 在手指离开屏幕后,就没有任何信息了;
- 获取的结果都是一个TouchList类数组集合,记录每一根手指的信息;
- ev.changeTouches [0]:第一根手指的信息
- clientX / clientY
- pageX / pageY
- …
- ev.changeTouches [0]:第一根手指的信息
- touches / changeTouches 都是用来记录手指信息的,常用changeTouches
- 普通事件对象Event:=>
document.onload = function(ev){console.log(ev)}
手指事件对象适用于移动端,PC端需要打开控制台,开启手机模拟器开启手机模拟器:点击鼠标右键->检查 / 查看元素
事件的传播机制
事件是浏览器赋予元素默认的行为操作,我们只要触发这些操作,就一定会触发相关事件,如果绑定了DOM0/DOM2级事件,在事件触发的时候可以添加一些操作,做一些事情,如果不添加,事件也会被触发,但不做任何处理。
当前元素的某个事件行为被触发,一定会经历三个阶段
- 捕获阶段:从最外层容器一直向里层查找,直到找到当前触发的事件源为止,查找的目的是建立起来当前元素未来冒泡传播的传播路线
- CAPTURING_PHASE: 1 捕获阶段
- 目标阶段:把当前元素的相关行为触发(如果绑定了方法,则把方法执行)
- AT_TARGET: 2 目标阶段
- 冒泡传播:不仅当前元素的相关行为会被触发,而且在捕获阶段获取的传播路径中的每一个元素的相关事件行为都会被触发【顺序从内到外】,而且父级元素的所有相关事件行为也会被触发,如果也对应了绑定的方法,方法也会被触发执行
- BUBBLING_PHASE: 3 冒泡阶段
- DOM0事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的
- DOM2可以控制在捕获阶段执行(xxx.addEventListener(‘xxx’,xxx,true) 最后参数true就是控制在捕获阶段执行,只是一般都不用)
- true 代表在捕获阶段执行此方法
- false 代表在冒泡阶段执行此方法
事件的传播机制示例图:
JS代码:
运行结果:
如果需要阻止事件的冒泡传播,绑定的事件函数内的头部添加ev.stopPropagation()
代码如图:
方法二:
- 因为点击事件行为存在冒泡传播机制,所以不论点击INNER/OUTER/BOX,最后都会传播到BODY上,触发BODY的CLICK事件行为。把为其绑定的方法执行;
- 在方法执行接受的事件对象中,有一个target/SRCElement属性(事件源),可以知道当前操作的是谁,,此时方法中完全可以根据事件源的不同,作不同的处理。
- 上述机制就是“事件委托/ 事件代理”:利用事件的冒泡传播机制(核心 / 前提),可以把一个“容器A”中所有的后代元素的某个“事件行为E”触发要做的操作,委托给A的事件行为,这样后期只要触发A中的任何元素的E行为,都会传播到A上,把给A绑定的方法执行;在方法执行的时候,基于事件源不同做不同的处理;
事件委托特点和好处:
- 性能高60%左右
- 可以操作动态绑定的元素
- 某些需求必须基于事件委托完成
- …
阻止事件默认行为
默认行为:浏览器会赋予元素很多默认的行为操作
- 鼠标右键点击
- A标签点击默认实现页面跳转/锚点定位
- 文本输入内容
- 部分浏览器会记录输入记录,在下一次输入的时候有模糊匹配
- 键盘按下会有内容输入
- …
我们可以基于ev.preventDefault()来禁止默认行为(也可以使用 return false)
//案例:禁用右键菜单,展示自己写的内容
window.oncontextmenu = function(ev){
//阻止默认行为,禁用右键菜单
ev.preventDefault();
//没有右键菜单则创建一个
let contextmenu = document.querySelector(".contextmenu");
if(!contextmenu){
contextmenu = document.createElement("div");
contextmenu.className = "contextmenu";
contextmenu.innerHtml = `<ul>
<li>跳转到详情页</li>
<li>进入到详情页</li>
<li>逗你玩</li>
</ul>`
document.body.appendChild(contextmenu);
};
//控制右键菜单位置
contextmenu.style.left = `${ev.clentX+10}px`;
contextmenu.style.top = `${ev.clentY+10}px`;
}
//点击其他内容(不包含contextmenu及里面内容),自定义右键菜单消失
window.onclick = function(ev){
let target = ev.target,
targetTag = target.tagName;
//点击contextmenu,不做任何处理
if(targetTag ==="LI"){
target = target.parentNode;
targetTag = target.tagName;
}
if(targetTag ==="UL"&& target.parentNode){return;}
//否则让右侧的菜单消失
let contextmenu = document.querySelector(".contextmenu");
if(contextmenu){document.body.removeChild(contextmenu);}
}
阻止事件冒泡
- ev.stopPropagation()
- mouseenter
mouseover VS mouseenter
- mouseover本身不是进入,而是看鼠标在谁上面,从子元素进入父元素,触发父元素的mouseover,从父元素进入子元素,触发子元素的mouseover
- mouseenter默认阻止了事件的冒泡传播,从父元素进入子元素,从子元重新素进入父元素,父元素的enter和leave都不会触发
- 它认为鼠标还在父元素中,不存在离开和重新进入
- 容器中有后代元素,尽可能用mouseenter
- 需要就冒泡传播干什么事情的时候,只能用mouseover
图形解释:
- 在上图点击一次执行了四个方法,每个方法中都有EV事件对象,此时四个对象是同一个;
- 事件对象和在哪个方法中出现没有关系,事件对象时用来记录当前操作的相关信息的
- 也就是操作一次记录一次,此时不论在哪取得EV都是记录本次操作的信息,除非重新操作,那么上一次信息就没有了
- ev存储的是最新的操作信息,只和操作有关,跟元素和函数没有关系!
事件委托
事件代理(event delegation):又称事件委托,是利用事件冒泡,把原本需要绑定的一个或多个事件委托给父元素。
需求: 项目中从新闻的列表页,添加点击列表进入新闻详情页的功能,一般都是委托给列表的父元素,因为新闻列表为动态添加的,所以click无法给动态添加的元素添加事件,需要借助事件委托来给列表添加事件;
原因:因为新闻列表为动态添加的,在document.ready时不存在,所以无法绑定元素事件,无法对事件进行响应,因此需要把事件委托给已有的父级元素。
<!--html -->
<ul class="father">
<!--li是后期动态渲染的 -->
<li class="son" data-id="000">详情1</li>
<li class="son" data-id="401">详情2</li>
<li class="son" data-id="203">详情n</li>
</ul>
<script>
//方法一:
//delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数。
$(".father").delegate("li.son","click",function(){
var links = $(this).attr("data-id");//路径参数
window.location = ...;
//...
};
//方法二:
//在原生js里面是通过event对象的taget属性实现
$("ul").click(function(event){
// 兼容性处理
var event = event || window.event;
var target = event.target || event.srcElement;
if(target.nodeName == "LI"){
var links = $(this).attr("data-id");//路径参数
window.location = ...;
//...
};
//方法三:
$(document).on('click','li.son',function(){
//$(this)指的就是被点击的元素
})
//据说不兼容苹果手机的。需要在元素css中添加cursor: pointer;
</script>
```前两种方法都是用过且可以实现的方法,第三种据说有兼容行,没有试验,前两种方法足够了。