事件
- 在元素对象有一些天生自带的属性名,比如onclick、onmouseover… 当鼠标触发了这些相关的操作,那么就会执行这些属性对应的属性值函数;
- 事件是元素天生自带的一种行为
- 事件是浏览器的一种行为,也可以是用户行为
- 发生在HTML元素上的事
事件绑定
- DOM0级事件绑定:都是冒泡阶段的绑定
- DOM2级事件可以控制阶段
添加事件的几种方法(DOM0级)
行内加
<div id="box" onclick="fn()">可以点击</div>
<script>
function fn(){
console.log(100);
}
js加
box.onclick = fn;
jquery加
$("#box").click(fn);
$("#box").on("click",fn);
</script>
- addEventListener(事件行为(去掉on),事件回调函数,布尔) ( DOM2级事件绑定 )
- 第三个参数是布尔值:false代表冒泡阶段执行, true:捕获阶段执行
- 只能给同一个元素的同一个事件行为绑定同一个方法,如果都相同,就会覆盖
- 可以给同一个元素的同一个事件行为绑定多个不同的方法
- removeEventListener() 移除事件,需要和添加的时候参数保持一致
- 只有元素才能调用这个方法,因为只有元素的原型链上才有
- 先绑定谁,谁先执行
- 函数中的this指向被点击的那个元素
- 正序执行(包括 IE9、IE10、IE11)
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
//
box.addEventListener("click",fn1,true);
box.removeEventListener("click",fn1,false);
//
box.addEventListener("mouseover",fn1,false)
box.addEventListener("click",fn2,false);
//
box.addEventListener("click",fn1,false)
box.addEventListener("click",fn2,false)
//只会执行一次,执行第二个
- attachEvent(“on”+事件,fn) ( DOM2级事件绑定 )
- 在IE8以上不兼容的; 谷歌不能用
- 倒序执行(在IE8)
- 方法中的this指向全局下的window对象
- 可以重复绑定
- detachEvent 删除事件
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
box.attachEvent("onclick",fn1)
box.attachEvent("onclick",fn2)
box.attachEvent("onclick",fn1)
box.attachEvent("onclick",fn1)
//每个fn1都会执行
事件对象
- 事件对象:当用户通过鼠标键盘去操作或触发元素的事件行为时,浏览器会默认将一些事件的信息传递给这个函数的第一个参数(比如鼠标点击的位置距离页面左右的距离,或距离点击元素边框的距离)
- clientX : 当前鼠标点击的位置距离可视窗口左边的距离
- clientY : 当前鼠标点击的位置距离可视窗口上边的距离
- offsetX : 当前鼠标点击的位置距离盒子左边框的距离
- offsetY : 当前鼠标点击的位置距离盒子上边框的距离
- pageX :当前鼠标点击的位置距离页面左边框的距离
- pageY :当前鼠标点击的位置距离页面上边框的距离
- target :事件源,事件在哪个元素上触发,事件源就是谁
- type :事件类型(‘click’)
- e.cancelBubble=true :取消事件默认的冒泡传播
- e.stopPropagation();// IE8及以下不兼容 取消事件默认的冒泡传播
- 在IE8及以下,浏览器将事件信息放到了window.event上,并没有传递给函数的形参
box.onclick=function(e){
e=e||window.event;
}
事件的默认行为
- a标签的点击默认跳转就是a的默认行为
- form表单subimt,会默认提交
- 阻止事件的默认行为:
- 形参.preventDefault() : 阻止事件的默认行为
- 形参.returnValue = false
<form action="">
<input type="text" name="user">
<input type="text" value="提交">
</form>
<a href="" id="abc">点我一下</a>
<script>
var abc = document.getElementById("abc");
abc.onclick = function(e){
//e.preventDefault();
e.returnValue=false;
}
</script>
input框事件
- onfocus : 获取鼠标焦点
- onblur : 失去鼠标焦点
- onchange : 当鼠标离开input框,并且input框中的内容发生改变,会触发改事件
- oninput : 当input框每改变一次,就会执行一次
- onkeydown : 当键盘按下时,触发的事件(获取到上一次的值)
- onkeyup : 当键盘抬起获取到最新的值
<input type="text" id="btn">
<script>
let btn = document.getElementById("btn");
btn.onfocus=function(){
console.log(100);
}
btn.onclick = function(){
console.log(200);
}
btn.onblur=function(){
console.log(300);
}
btn.onchange=function(){
console.log(400);
}
btn.oninput=function(e){
console.log(e);
console.log(this.value); //获取input框的值
}
btn.onkeydown=function(){
console.log(this.value);
}
btn.onkeyup=function(e){
console.log(e);// 键盘事件对象
// 在键盘上,每一个键盘的键都有一个对应的keyCode;根据keyCode可以判断当前点击的是哪一个键;
}
</script>
keyCode表
事件的传播
- 捕获阶段–>目标阶段–>冒泡阶段
- 冒泡传播
- 事件有冒泡传播的机制 : 当触发子元素的事件时,会依次触发当前元素祖先元素上对应的事件
- 取消事件的默认冒泡传播
- 形参.cancelBubble = true;
- 形参.stopPropagation(); IE8及以下不兼容
- center.addEventListener(“click”,fn4,true)
- 第三个参为true时捕获阶段执行,从外向里
- 为false时冒泡阶段执行,从里向外
- 当找到事件源,谁先绑定谁先执行
- 事件冒泡执行过程:
从最具体的的元素(你单击的那个元素)开始向上开始冒泡,拿我们上面的案例讲它的顺序是:child->box
事件捕获执行过程:
从最不具体的元素(最外面的那个盒子)开始向里面冒泡,拿我们上面的案例讲它的顺序是:box->child
<style>
*{
margin: 0;
padding: 0;
}
#outer{
width: 300px;
height: 300px;
background: red;
margin: 100px auto;
}
#inner{
width: 200px;
height: 200px;
background: green;
margin: auto;
}
#center{
width: 100px;
height: 100px;
background: yellow;
margin: auto;
}
</style>
<body>
<div id="outer">
<div id="inner">
<div id="center"></div>
</div>
</div>
<script>
let outer = document.getElementById('outer');
let inner= document.getElementById('inner');
let center = document.getElementById('center');
function fn1(e) {
console.log('center');
}
function fn2(e) {
console.log('inner');
}
function fn3(e) {
console.log('outer');
}
//冒泡阶段
outer.onclick = function (e) {
// console.log(e.target);
console.log('outer');
}
inner.onclick = function (e) {
// console.log(e.target);
console.log('inner');
}
center.onclick = function (e) {
// console.log(e.target);
console.log('center');
}
//fn1 fn2 fn3
//执行阶段
center.addEventListener('click',fn1,true);
inner.addEventListener('click',fn2,true);
outer.addEventListener('click',fn3,true);
//fn3 fn2 fn1
//冒泡执行阶段
function fn1(e) {
console.log('center 冒泡');
}
function fn4(e) {
console.log('center 捕获');
}
function fn2(e) {
console.log('inner 冒泡');
}
function fn5(e) {
console.log('inner 捕获');
}
function fn3(e) {
console.log('outer 冒泡');
}
function fn6(e) {
console.log('outer 捕获');
}
center.addEventListener('click',fn1,false);
inner.addEventListener('click',fn2,false);
outer.addEventListener('click',fn3,false);
center.addEventListener('click',fn4,true);
inner.addEventListener('click',fn5,true);
outer.addEventListener('click',fn6,true);
// fn6 fn5 fn1 fn4 fn2 fn3
事件委托
- 事件委托:主要利用事件的冒泡传播机制,给最外层的盒子绑定事件,根据事件源的不同,进行判断,处理不一样的需求
<div id="parent">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
//三个div将onmouseover这个事件委托给这三个盒子的父元素
<script>
let parent = document.getElementById("parent");
parent.onmouseover=function(e){
if(e.target.innerHTML==="1"){
console.log("红色");
}else if(e.target.innerHTML==="2"){
console.log("绿色");
}else if(e.target.innerHTML==="3"){
console.log("黄色");
}
}
</script>
封装拖拽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
#box,#box1{
width:100px;
height:100px;
background: red;
position: absolute;
left:0;
top:0;
}
#box1{
top:200px;
}
</style>
</head>
<body>
<div id="box">
</div>
<div id="box1"></div>
<script>
function Drag(str){
// this--> Drag的实例;可以获取到当前Drag原型上的方法
let ele = document.getElementById(str.slice(1));
this.ele = ele;// 把元素放到了实例this的自定义属性上;
this.ele.onmousedown = this.down.bind(this);
}
Drag.prototype.down =function(e){
// 记录位置 绑定onmousemove 和onmouseup;
// console.log(e);
console.log(this);// Drag的实例;需要将该函数中的this指向Drag的实例;方便后期使用;
this.x = e.clientX;
this.y = e.clientY;
this.startL = parseFloat(getComputedStyle(this.ele).left);
this.startT = parseFloat(getComputedStyle(this.ele).top);
document.onmousemove = this.move.bind(this);
document.onmouseup = this.up;
}
Drag.prototype.move =function(e){
// 用鼠标变化的距离+ 盒子初始的位置=盒子最新的位置;
this.ele.style.left = e.clientX-this.x+this.startL+"px";
this.ele.style.top = e.clientY-this.y+this.startT+"px";
}
Drag.prototype.up =function(){
// 清除document的事件上的方法;
document.onmousemove = null;
document.onmouseup = null;
}
new Drag("#box");
new Drag("#box1")
// let obj = {
// fn:function(){
// console.log(this);
// }
// }
// obj.fn();
// box.onclick = obj.fn;
</script>
</body>
</html>
事件循环机制(Event Loop)
微任务 : await ,promise的then
宏任务: 定时器 ajax
事件循环机制: JS代码执行分为主任务队列和等待任务队列;在执行主栈的代码遇到同步的代码会立即执行,遇到异步的代码会先放到等待队列中,放入时区分是宏任务还是微任务,按照不同的任务放到等待队列不同的池子中;当主栈执行完成时,那么要先去等待队列的微任务中去遍历,按照放入时间先后依次执行,把微任务放到主栈中去执行,微任务执行完毕,再去执行宏任务。
function fn1(){console.log(666);}
setTimeout(function(){
console.log(800);
},0)
console.log(900);
async function fn(){
console.log(999);
await fn1();
console.log(888);
}
let p = new Promise(function(resolve,reject){
console.log(200);
resolve();
console.log(300);
})
p.then(function(){
console.log(100);// 异步的
});
fn();
// 900 200 300 999 666 888 100 800
// 微任务执行的顺序要看谁先放进任务队列中,谁先执行;
// async function async1() {
// console.log('async1 start');// 2
// await async2();// await 后面的是同步
// console.log('async1 end'); // 微1 6
// }
// async function async2() {
// console.log('async2'); // 3
// }
// console.log('script start');// 1
// setTimeout(() => {
// console.log('setTimeout'); // 8
// }, 0);
// async1();
// new Promise(resolve => {
// console.log('promise1');// 4
// resolve();
// }).then(() => {
// console.log('promise2');// 微2 7
// });
// console.log('script end'); // 5
// 微任务 : await promise的then
// 宏任务: 定时器 ajax
// 事件循环机制: JS代码执行分为主任务队列和对待任务队列;
在执行主栈的代码遇到同步的代码会立即执行,遇到异步的代码会先放到等待队列中,
放入时区分是宏任务还是微任务,按照不同的任务放到等待队列不同的池子中;当主栈执行完成时,
那么要先去等待队列的微任务中去遍历,按照放入时间先后依次执行,把微任务放到主栈中去执行,
微任务执行完毕,再去执行宏任务。
//
console.log('1');
setTimeout(function () {
console.log('2');
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5');
});
});
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8');
});
setTimeout(function () {
console.log('9');
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12');
});
});
事件详解
查阅更多事件方式:
https://developer.mozilla.org/zh-CN/docs/Web/Events
或者查看元素的属性(属性中onxxx就是元素拥有的事件行为)
一、常用事件
1. 鼠标事件
click 点击(移动端 click 被识别为单击)
- dblclick 双击
- mousedown 鼠标按下
- mouseup 鼠标抬起
- mousemove 鼠标移动
- mouseover 鼠标滑过
- mouseout 鼠标滑出
- mouseenter 鼠标进入
- mouseleave 鼠标离开
- mousewhell 鼠标滚轮滚动
- 鼠标滚轮
/*注册事件*/
/*IE、Opera注册事件*/
if (document.attachEvent) {
outer1.attachEvent("onmousewheel", self.scrollFunc);
}
//Firefox使用addEventListener添加滚轮事件
if (document.addEventListener) {
//firefox
outer1.addEventListener(
"DOMMouseScroll",
self.scrollFunc,
false
);
}
//Safari与Chrome属于同一类型
outer1.onmousewheel = self.scrollFunc;
// 滚轮
scrollFunc(e) {
e = e || window.event;
console.dir(e);
if (e.wheelDelta) {
//判断浏览器IE,谷歌滑轮事件
if (e.wheelDelta > 0) {
//当滑轮向上滚动时
// alert("上滚");
}
if (e.wheelDelta < 0) {
//当滑轮向下滚动时
// alert("下滚");
}
} else if (e.detail) {
//Firefox滑轮事件
if (e.detail > 0) {
//当滑轮向下滚动时
// alert("下滚");
}
if (e.detail < 0) {
//当滑轮向上滚动时
// alert("上滚");
}
}
},
2. 键盘事件
key…
- keydown 按下某个键
- keyup 抬起某个键
- keypress 除 shift / Fn / CapsLock 键以外,其他键按住(连续触发)
- 监听组合键 (ctrl+F)
//css <style> .box { display: none; } .success{ display: block; } </style> //html <div class="box"> 666666666 </div> //js <script> document.onkeydown=(e)=>{ console.log(e); if(e.ctrlKey&&e.keyCode===70){ document.querySelector(".box").classList.add("success") document.querySelector(".box").classList.remove("box") } } </script>
3. 移动端手指事件
单手指事件模型 Touch
- touchstart 手指按下
- touchmove 手指移动
- touchend 手指松开
- touchcancel 操作取消(一般应用于非正常状态下操作结束)
多手指事件模型 Gestrue
- gestruestart
- gesturechange / gestrueundate
- gestureend
- gesturecancel
4. 表单元素常用事件
- onchange : 当鼠标离开input框,并且input框中的内容发生改变,会触发改事件
- onfocus : 获取鼠标焦点
- onblur : 失去鼠标焦点
- oninput : 当input框每改变一次,就会执行一次
- onkeydown : 当键盘按下时,触发的事件(获取到上一次的值)
- onkeyup : 当键盘抬起获取到最新的值
5. 音视频常用事件
- canplay 可以播放(资源没有加载完,播放中可能会卡顿)
- canplaythrough 可以播放(资源已经加载完,播放中不会卡顿)
- play 开始播放
- playing 播放中
- pause 暂停播放
6. 其他常用事件
- load 资源加载完
- unload 资源卸载
- beforeunload 当前页面关闭之前
- error 资源加载失败
- scroll 滚动事件
- readystatechange AJAX请求状态改变事件
- contextmenu 鼠标右键触发
二、DOM 0级事件
dom 0级事件绑定:
dom 0级事件绑定的原理:给元素的私有属性赋值,当事件触发,浏览器会帮我们把赋的值执行,但是这样也导致 “只能给当前元素某一个事件行为绑定一个方法”
元素.on事件行为=function(){}
box.onclick = function () {
console.log('哈哈哈~~');
}
box.onclick = function () {
console.log('呵呵呵~~');
}
box.onclick = function () {
console.log('哈哈哈~~');
// 移除事件绑定:DOM0 直接赋值为 null 即可
box.onclick = null;
}
三、DOM 2级事件
dom 2级事件绑定:
dom 2级事件绑定的原理:基于原型链查找机制,找到 EventTarget.prototype 上的方法并且执行,此方法执行,会把给当前元素某个事件行为绑定的所有方法,存放到浏览器默认的事件池中(绑定几个方法,会向事件池存储几个),当事件行为触发,会把事件池中存储的对应方法,依次按照顺序执行 “给当前元素某一个事件行为绑定多个不同方法”
元素.addEventListener(事件行为,function(){},true/false)
IE6~8中:元素.attachEvent('on事件行为',function(){})
box.addEventListener('click', function () {
console.log('哈哈哈~~');
}, false);
box.addEventListener('click', function () {
console.log('呵呵呵~~');
}, false);
function fn() {
console.log('哈哈哈~~');
// 移除事件绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)
box.removeEventListener('click', fn, false);
}
box.addEventListener('click', fn, false);
- dom 2级事件绑定的时候我们一般都采用实名函数
- 目的:这样可以基于实名函数去移除事件绑定
- 基于 addEventListener 向事件池增加方法,存在去重机制:“同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
- dom 0级事件和 dom 2级事件可以混在一起用:执行的顺序以绑定的顺序为主
box.addEventListener('click', function () {
console.log('哔咔哔咔~~');
});
box.onclick = function () {
console.log('哇咔咔~~');
}
box.addEventListener('click', function () {
console.log('call~~');
});
- dom 0级事件中能做的事件行为,dom 2级事件都支持,dom 0级事件不一定能处理绑定,例如:transitionend、DOMContentLoaded…
window.addEventListener('load', function () {
// 所有资源都加载完成触发
console.log('LOAD');
});
window.addEventListener('DOMContentLoaded', function () {
// 只要DOM结构加载完就会触发
console.log('DOMContentLoaded');
});
// $(document).ready(function(){})
$(function () {
// JQ 中的这个处理(DOM 结构加载完触发)采用的就是 DOMContentLoaded 事件,并且依托 DOM2 事件绑
// 定来处理,所以同一个页面中,此操作可以被使用多次
});
$(function () {
}); */
// JQ 中的事件绑定采用的都是 DOM2 事件绑定,例如:on / off / one
- window.onload VS $(document).ready()
- $(document).ready() 采用的是 DOM2 事件绑定,监听的是 DOMContentLoaded 这个事件,所以只要DOM 结构加载完成就会被触发执行,而且同一个页面中可以使用多次(绑定不同的方法,因为基于 DOM2 事件池绑定机制完成的)
- window.onload 必须等待所有资源都加载完成才会被触发执行,采用 DOM0 事件绑定,同一个页面只能绑定一次(一个方法),想绑定多个也需要改为 window.addEventListener(‘load’, function () {})DOM2 绑定方式
三、给元素的事件行为绑定方法
给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 =》事件对象
- 如果是鼠标操作:获取的是 MouseEvent 类的实例 =》 鼠标事件对象
鼠标事件对象 -> MOuseEvent.prototoye -> UIEvent.prototype ->Event.prototype -> Object.prototype- 如果是键盘操作:获取的是KeyboardEvent类的实例 =》 键盘事件对象
- 除了以上还有:普通事件对象(Event)、手指事件对象(TouchEvent)等
事件对象
box.onclick = function (ev) {
// 鼠标事件对象
// clientX / clientY:当前鼠标触发点距离当前窗口左上角的X / Y轴坐标
// pageX / pageY:触发点距离当前页面左上角的X / Y轴坐标
// type:触发事件的类型
// target:事件源(操作的是哪个元素,哪个元素就是事件源),在不兼容的浏览器中可以使用srcElement
获取,也代表的是事件源
// preventDefault():用来阻止默认行为的方法,不兼容的浏览器中用 ev.returnValue = false 也可以
阻止默认行为
// stopPropagation():阻止冒泡传播,不兼容的浏览器中用 ev.cancelBubble = true 也可以阻止默认
行为
console.log(ev);
}
四、当事件触发时浏览器会做什么
事件对象和函数以及给谁绑定的事件没啥必然关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,所以在那个方法中获取的信息都是一样的,第二次操作,存储的信息会把上一次操作存储的信息替换掉…
每一次事件触发,浏览器都会这样处理一下:
- 捕获到当前操作的行为(把操作信息获取到),通过创建 MouseEvent 等类的实例,得到事件对象 EV
- 通知所有绑定的方法(符和执行条件的)开始执行,并且把 EV 当作实参传递给每个方法,在每个方法中得到事件对象
- 后面再重新触发这个事件行为,会重新获取本次操作的信息,用新的信息替换老的信息,然后继续之前的步骤
let obj = null;
box.addEventListener('click', function (ev) {
console.log(ev);
obj = ev;
});
box.addEventListener('click', function (ev) {
console.log(ev === obj); // true
});
document.body.onclick = function (ev) {
console.log(ev === obj); // true
}
五、事件传播机制
事件的传播机制:
- 捕获阶段:从最外层向最里层事件源依次进行查找(目的:是为冒泡阶段事先计算好传播的层级路径) CAPTURING_PHASE
- 目标阶段:当前元素的相关事件行为触发 AT_TARGET
- 冒泡传播:触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到外的顺序) BUBBLING_PHASE
let $ = selector => document.querySelector(selector);
let inner = $('.inner');
let outer = $('.outer');
inner.onclick = function (e) {
console.log('inner 的click事件触发了');
};
outer.onclick = function (e) {
console.log('outer 的click事件触发了');
e.stopPropagation(); // 阻止事件冒泡
};
document.body.onclick = function () {
console.log('body 的click事件被触发了');
};
document.onclick = function () {
console.log('document 的click事件触发了');
};
事件冒泡
事件冒泡:我们点击 inner,inner 的父级元素 outer 的点击事件以及整个文档顶端的 document 的点击事件都会被依次触发。这种从低层级的 html 元素向高层级依次触发事件的现象称为事件冒泡;
取消冒泡:e.stopPropagation() 阻止事件冒泡;
IE 的阻止冒泡: e.cancelBubble = true;