JS事件(常用事件,事件传播,事件循环)

事件

  1. 在元素对象有一些天生自带的属性名,比如onclick、onmouseover… 当鼠标触发了这些相关的操作,那么就会执行这些属性对应的属性值函数;
  2. 事件是元素天生自带的一种行为
  3. 事件是浏览器的一种行为,也可以是用户行为
  4. 发生在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>
  1. addEventListener(事件行为(去掉on),事件回调函数,布尔) ( DOM2级事件绑定
    1. 第三个参数是布尔值:false代表冒泡阶段执行, true:捕获阶段执行
    2. 只能给同一个元素的同一个事件行为绑定同一个方法,如果都相同,就会覆盖
    3. 可以给同一个元素的同一个事件行为绑定多个不同的方法
    4. removeEventListener() 移除事件,需要和添加的时候参数保持一致
    5. 只有元素才能调用这个方法,因为只有元素的原型链上才有
    6. 先绑定谁,谁先执行
    7. 函数中的this指向被点击的那个元素
    8. 正序执行(包括 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)
		//只会执行一次,执行第二个 
  1. attachEvent(“on”+事件,fn) DOM2级事件绑定
    1. 在IE8以上不兼容的; 谷歌不能用
    2. 倒序执行(在IE8)
    3. 方法中的this指向全局下的window对象
    4. 可以重复绑定
    5. 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都会执行

事件对象

  1. 事件对象:当用户通过鼠标键盘去操作或触发元素的事件行为时,浏览器会默认将一些事件的信息传递给这个函数的第一个参数(比如鼠标点击的位置距离页面左右的距离,或距离点击元素边框的距离)
    1. clientX : 当前鼠标点击的位置距离可视窗口左边的距离
    2. clientY : 当前鼠标点击的位置距离可视窗口上边的距离
    3. offsetX : 当前鼠标点击的位置距离盒子左边框的距离
    4. offsetY : 当前鼠标点击的位置距离盒子上边框的距离
    5. pageX :当前鼠标点击的位置距离页面左边框的距离
    6. pageY :当前鼠标点击的位置距离页面上边框的距离
    7. target :事件源,事件在哪个元素上触发,事件源就是谁
    8. type :事件类型(‘click’)
    9. e.cancelBubble=true :取消事件默认的冒泡传播
    10. e.stopPropagation();// IE8及以下不兼容 取消事件默认的冒泡传播
  2. 在IE8及以下,浏览器将事件信息放到了window.event上,并没有传递给函数的形参
box.onclick=function(e){
  e=e||window.event;
}

事件的默认行为

  1. a标签的点击默认跳转就是a的默认行为
  2. form表单subimt,会默认提交
  3. 阻止事件的默认行为:
    1. 形参.preventDefault() : 阻止事件的默认行为
    2. 形参.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框事件

  1. onfocus : 获取鼠标焦点
  2. onblur : 失去鼠标焦点
  3. onchange : 当鼠标离开input框,并且input框中的内容发生改变,会触发改事件
  4. oninput : 当input框每改变一次,就会执行一次
  5. onkeydown : 当键盘按下时,触发的事件(获取到上一次的值)
  6. 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表

在这里插入图片描述

事件的传播

  1. 捕获阶段–>目标阶段–>冒泡阶段
  2. 冒泡传播
    1. 事件有冒泡传播的机制 : 当触发子元素的事件时,会依次触发当前元素祖先元素上对应的事件
    2. 取消事件的默认冒泡传播
      1. 形参.cancelBubble = true;
      2. 形参.stopPropagation(); IE8及以下不兼容
  3. center.addEventListener(“click”,fn4,true)
    1. 第三个参为true时捕获阶段执行,从外向里
    2. 为false时冒泡阶段执行,从里向外
    3. 当找到事件源,谁先绑定谁先执行
  4. 事件冒泡执行过程:

从最具体的的元素(你单击的那个元素)开始向上开始冒泡,拿我们上面的案例讲它的顺序是: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

事件委托

  1. 事件委托:主要利用事件的冒泡传播机制,给最外层的盒子绑定事件,根据事件源的不同,进行判断,处理不一样的需求
	<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 被识别为单击)

  1. dblclick 双击
  2. mousedown 鼠标按下
  3. mouseup 鼠标抬起
  4. mousemove 鼠标移动
  5. mouseover 鼠标滑过
  6. mouseout 鼠标滑出
  7. mouseenter 鼠标进入
  8. mouseleave 鼠标离开
  9. 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…

  1. keydown 按下某个键
  2. keyup 抬起某个键
  3. keypress 除 shift / Fn / CapsLock 键以外,其他键按住(连续触发)

3. 移动端手指事件

单手指事件模型 Touch

  1. touchstart 手指按下
  2. touchmove 手指移动
  3. touchend 手指松开
  4. touchcancel 操作取消(一般应用于非正常状态下操作结束)

多手指事件模型 Gestrue

  1. gestruestart
  2. gesturechange / gestrueundate
  3. gestureend
  4. gesturecancel

4. 表单元素常用事件

  1. onchange : 当鼠标离开input框,并且input框中的内容发生改变,会触发改事件
  2. onfocus : 获取鼠标焦点
  3. onblur : 失去鼠标焦点
  4. oninput : 当input框每改变一次,就会执行一次
  5. onkeydown : 当键盘按下时,触发的事件(获取到上一次的值)
  6. onkeyup : 当键盘抬起获取到最新的值

5. 音视频常用事件

  1. canplay 可以播放(资源没有加载完,播放中可能会卡顿)
  2. canplaythrough 可以播放(资源已经加载完,播放中不会卡顿)
  3. play 开始播放
  4. playing 播放中
  5. pause 暂停播放

6. 其他常用事件

  1. load 资源加载完
  2. unload 资源卸载
  3. beforeunload 当前页面关闭之前
  4. error 资源加载失败
  5. scroll 滚动事件
  6. readystatechange AJAX请求状态改变事件
  7. 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);
  1. dom 2级事件绑定的时候我们一般都采用实名函数
  2. 目的:这样可以基于实名函数去移除事件绑定
  3. 基于 addEventListener 向事件池增加方法,存在去重机制:“同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
  4. dom 0级事件和 dom 2级事件可以混在一起用:执行的顺序以绑定的顺序为主
box.addEventListener('click', function () {
		console.log('哔咔哔咔~~');
});
box.onclick = function () {
		console.log('哇咔咔~~');
}
box.addEventListener('click', function () {
		console.log('call~~');
});
  1. 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()
  1. $(document).ready() 采用的是 DOM2 事件绑定,监听的是 DOMContentLoaded 这个事件,所以只要DOM 结构加载完成就会被触发执行,而且同一个页面中可以使用多次(绑定不同的方法,因为基于 DOM2 事件池绑定机制完成的)
  2. window.onload 必须等待所有资源都加载完成才会被触发执行,采用 DOM0 事件绑定,同一个页面只能绑定一次(一个方法),想绑定多个也需要改为 window.addEventListener(‘load’, function () {})DOM2 绑定方式

三、给元素的事件行为绑定方法

给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 =》事件对象

  1. 如果是鼠标操作:获取的是 MouseEvent 类的实例 =》 鼠标事件对象
    鼠标事件对象 -> MOuseEvent.prototoye -> UIEvent.prototype ->Event.prototype -> Object.prototype
  2. 如果是键盘操作:获取的是KeyboardEvent类的实例 =》 键盘事件对象
  3. 除了以上还有:普通事件对象(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);
} 

四、当事件触发时浏览器会做什么

事件对象和函数以及给谁绑定的事件没啥必然关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,所以在那个方法中获取的信息都是一样的,第二次操作,存储的信息会把上一次操作存储的信息替换掉…

每一次事件触发,浏览器都会这样处理一下:

  1. 捕获到当前操作的行为(把操作信息获取到),通过创建 MouseEvent 等类的实例,得到事件对象 EV
  2. 通知所有绑定的方法(符和执行条件的)开始执行,并且把 EV 当作实参传递给每个方法,在每个方法中得到事件对象
  3. 后面再重新触发这个事件行为,会重新获取本次操作的信息,用新的信息替换老的信息,然后继续之前的步骤
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
}

五、事件传播机制

事件的传播机制:

  1. 捕获阶段:从最外层向最里层事件源依次进行查找(目的:是为冒泡阶段事先计算好传播的层级路径) CAPTURING_PHASE
  2. 目标阶段:当前元素的相关事件行为触发 AT_TARGET
  3. 冒泡传播:触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到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;


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Sheng_zhenzhen/article/details/106797855