💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[toc] ## 什么是事件 什么是事件? 事件是分为两部分: ### 行为本身 浏览器天生就赋予其的行为,比如说 - onclick - onmouseover - onmouseenter - onmouseout(onmouseleave) - onmousemove - onmousedown - onmouseup - onmousewheel - onscroll - onresize(window.onresize浏览器窗口的大小改变事件) - onload(所有资源都加载完成) - onunload(浏览器关闭的时) - onfocus(文本框获取焦点行为) - onblur(文本框失去焦点行为) - onkeydown (必须是文本框?) - onkeyup 哪怕我们没有给上述的行为绑定方法,事件也是存在的,当我们点击这个盒子的时候,同样会触发它的onclick行为,只是什么事情都没做 ### 事件绑定 给元素的某一个行为绑定方法 ``` var oDiv = document.getElementById("div1"); ``` DOM 【0级】事件绑定 ``` //onclick这个方法定义在当前元素的私有属性上 oDiv.onclick = function(){ //->当我们触发oDiv的click行为的时候,会把绑定的这个函数执行 }; ``` 为什么没有DOM 【1级】? DOM1级升级的时候并没有对事件系统进行升级,故没有 DOM 【1级】事件绑定 DOM 【2级】事件绑定 ``` //addEventListener这个属性是定义在当前元素的祖先身上的->EventTarget这个类的原型身上 oDiv.addEventListener("click",function(){ console.log("ok"); },false); ``` ## 事件对象及兼容 ``` //->我们把匿名函数定义的部分当做一个值赋给oDiv的点击行为(函数表达式) //->当我们触发#div1点击行为的时候,会执行对应绑定上的方法 //不仅仅把绑定的方法执行了,而且浏览器还默认的给这个方法传递了一个参数值 oDiv.onclick = function(){ console.dir(arguments) ; //->arguments[0]->MouseEvent //【鼠标事件对象】 //1)它是一个事件对象数据类型的值,里面包含了很多属性名和属性值,这些都是用来记录当前鼠标相关信息的 //2)MouseEvent->UIEvent->Event->Object //3)MouseEvent 记录的是页面中唯一一个鼠标每一次触发时候的相关信息,和到底是在哪个元素身上触发的值没有关系 }; ``` ### 事件对象 1. 它是一个事件对象数据类型的值,里面包含了很多属性名和属性值,这些都是用来记录当前鼠标相关信息的 2. MouseEvent->UIEvent->Event->Object 3. MouseEvent 记录的是页面中唯一一个鼠标每一次触发时候的相关信息,和到底是在哪个元素身上触发的值没有关系 ### 关于事件对象的兼容性问题 事件对象本身的获取存在兼容问题:标准浏览器中是浏览器给方法传递的参数,我们只需要定义形参e就可以获取到 在IE6~8中浏览器不会给方法传递参数,我们如果需要的话,需要到window.event中获取查找 ``` //->e = e || window.event; oDiv.onclick = function(e){ e=e||window.event; //->e.type: 存储的是当前鼠标触发的行为类型,比如这里是"click" //->e.clientX / e.clientY:当前鼠标触发点距离当前屏幕左上角的x/y轴的坐标值 //->e.target:事件源,当前鼠标触发的是哪个元素,那么它存储的就是哪个元素;但是在IE6~8中不存在这个属性,我们使用e.srcElement来获取事件源 e.target = e.target||e.srcElement; //-> e.pageX/e.pageY:当前鼠标触发点距离body左上角的x/y轴的坐标(页面第一屏幕最左上端)的x/y轴的坐标,但是在IE6~8中没有这个属性,我们通过使用clientY+滚动条卷去的高度来获取也可以 e.pageX = e.pageX||(e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft)); e.pageY = e.pageY||(e.clientY+(document.documentElement.scrollTop||document.body.scrollTop)); //->e.preventDefault:阻止浏览器的默认行为 //IE6~8没有这个方法 ev.preventDefault?e.preventDefault():e.returnValue=false; return false;//->和上述的代码是一样的效果,都是在阻止默认行为 //如果是a标签,还可以使用javascript:; 或 javascript:void 0 或 javascript:void 1 //->e.stopPropagation:阻止事件的冒泡传播,在IE6~8中不兼容 e.stopPropagation?e.stopPropagation():e.cancelBubble=true; }; ``` ``` document.body.onclick = function(e){ } ``` ``` input.onkeyup = function(e){ //->KeyboardEvent //->e.keyCode:当前键盘上每一个键对应的值 //空格键(space)->32 //回退键(Backspace)->8 //回车键(Enter)->13 //删除键(Del)->46 //四个方向键 左上右下-> 37 38 39 40 } document.body 也为KeyboardEvent ``` ### 默认行为 a标签:默认行为就是跳转页面,但是我们有时候使用a标签,只是想应用它的特殊性,并不想点击的时候跳转。 ## 事件的传播机制 ![](https://box.kancloud.cn/2645396574f3ac3c5222c25860c5afa7_710x340.png) 分为三个阶段(主要是捕获和冒泡) - 捕获阶段:从外向内依次查找元素 - 目标阶段:当前事件源本身的操作 - 冒泡阶段:从内到外依次触发相关的行为(我们最常用的就是冒泡阶段) ### 0级与冒泡 使用 DOM 【0级】绑定的事件,都是在行为触发后的冒泡阶段把方法执行的 >**注意:** > 每个浏览器传播到的最顶层是不一样的,`谷歌`中可以传播到`document`,但是在`IE`中只能到`html` > > 事件对象在传播过程中是同一个,或则说,一个页面中的event都是一样的(想象一下,在ie6~8下,event是在window下的一个属性) ### 2级与捕获 ``` //第一个参数:行为的类型 //第二个参数:给当前的行为绑定的方法 //第三个参数:控制在哪个阶段发生(true捕获阶段,false冒泡阶段,默认false) document.body.addEventListener("click",function(e){ console.log("body"); },false) ``` ## 事件委托(事件代理) **利用**事件的`冒泡传播机制`,触发当前元素的某个行为,它父级所有的元素的相关行为都会被触发,如果一个容器中有很多元素都要绑定点击事件,我们没有必要的一个个的绑定了,只需要给最外层的容器绑定一个点击事件即可,在这个方法执行的时候,通过`事件源`的区分来进行不同的操作。 不论点击body里的哪一个都会触发body上的click事件 ``` document.body.onclick = function(e){ e = e || window.event; console.log(e.target||e.srcElement); } ``` 一般应用在点击隐藏上 ## DOM二级事件 ### DOM【0级】存在的问题 只能给一个元素的一个行为绑定一个函数 ``` box.onclick = function(){} box.onclick = function(){} ``` ### DOM【2级】的优势 我们使用DOM2事件绑定,其实是让box通过原型链一直找到EventTarget这个内置类原型上的addEventListener方法实现的 #### 1)可以给某一个元素的同一个行为绑定多个不同的方法 ``` box.addEventListener("click",function(e){ console.log(1); },false); box.addEventListener("click",function(e){ console.log(2); },false); ``` #### 2)DOM【0级】的行为类型我们用DOM【2级】一样可以绑定, #### 3)新增事件【DOMContentLoaded】 而且DOM2中还提供了一些DOM0没有的行为类型->`DOMContentLoaded`:当页面中的DOM结构(HTML结构加载完成)触发的行为 ``` //这是指box里的HTML结构加载完成 //貌似box不行?只能document box.addEventListener("DOMContentLoaded",function(){ },false); document.addEventListener("DOMContentLoaded",function(){ },false); ``` 关于加载完成事件 ``` //当页面中的所有资源都加载完成(图片、HTML结构、音视频。。。)才会执行后面的函数;且一个页面中只能用一次,后面再写会把前面的覆盖掉;因为它是采用DOM 0级事件绑定,所以只能绑定一次 window.onload = function(){} //jquery中是只要当页面的HTML结构加载完成就会执行对应的函数;并且在同一个页面中可以出现多次 //说明采用的DOM2 的 DOMContentLoaded $(document).ready(function(){})->$(function(){}) ``` ``` //window.onload的 DOM【2级】形式 window.addEventListener("load",function(){},false) ``` ### 一些细节 DOM【2级】 一般绑定实名函数,便于移除 ``` var handleClick = function(e){ console.log(this);//this->box }; box.addEventListener("click",handleClick,false) //->移除的时候需要保证三个参数:行为、方法、哪个阶段发生 一点都不能差->在使用DOM2绑定的时候,我们一般都给他绑定的是实名函数 box.removeEventListener("click",handleClick,false) ``` 只能给某个元素的同一个行为绑定多个“不同”的方法(如果方法相同了只能留一个) ``` function fn1(){console.log(1)}; box.addEventListener("click",fn1,false); box.addEventListener("click",fn1,false); <<< 只会输出一次1 ``` #### 事件池 事件池是用来存储当前元素行为绑定的方法的(浏览器自带的机制) ![](https://box.kancloud.cn/c2750d18abc4e78c18b350d75a3fc94c_265x357.png) yy的>如果要给同一个元素的同一个行为绑定多个不同的方法可以使用数组作为参数传入 ``` function fn1(){} function fn2(){} function fn3(){} box.addEventListener("click",[fn1,fn2,fn3],false) ``` #### 兼容性 在IE6~8中我们不支持addEventListener/removeEventListener,如果想实现DOM2事件绑定只能用attchEvent/detachEvent 它只有两个参数,不能像addEventListener那样控制在哪个阶段发生,默认只能在冒泡阶段发生 且行为需要添加`on`(和DOM【0级】特别像) ``` box.attchEvent("onclick",fn1); ``` >[danger] 此方法绑定的事件的执行顺序不一定,是混乱的 >并且可以给同一个元素的同一个行为绑定多次相同的方法 >this指向的是window而不是元素本身 ``` /* * bind:处理DOM2级事件绑定的兼容性问题(绑定) * curEle ->要绑定的事件的元素 * evenType->要绑定的事件类型("click"、"mouseover"...) * evenFn->要绑定的方法 */ function bind(curEle,evenType,evenFn){ if(document.addEventListener){ return curEle.addEventListener(evenType,evenFn,false) } curEle.attachEvent("on"+evenType,evenFn); } function unbind(curEle,evenType,evenFn){ if(document.addEventListener){ return curEle.removeEventListener(evenType,evenFn,false) } curEle.detachEvent("on"+evenType,evenFn); } ``` 如何兼容this? ![](https://box.kancloud.cn/7d992115d3f630330342a2f6d4cf8b70_867x262.png) ``` var tempFn = function(){ fn1.call(box); }; box.attachEvent("onclick",tempFn); box.detachEvent("onclick",tempFn); ``` ``` var tempFn; function bind(curEle,evenType,evenFn){ if(document.addEventListener){ return curEle.addEventListener(evenType,evenFn,false) } //->给evenFn化妆,并且把化妆前的照片贴在自己对应的脑门上 tempFn = function(){ evenFn.call(curEle); }; tempFn.photo = evenFn; //->首先判断该自定义属性之前是否存在,不存在的话创建一个,由于要存储多个方法化妆后的结果,所以我们让其值是一个数字 if(!curEle["myBind"]){ curEle["myBind"] = []; } curEle["mybind"].push(tempFn); curEle.attachEvent("on"+evenType,tempFn); } function unbind(curEle,evenType,evenFn){ if(document.addEventListener){ return curEle.removeEventListener(evenType,evenFn,false) } var ary = curEle["myBind"]; for(var i=0;i<ary.length;++i){ if(ary[i].photo === evenFn){ curEle.detachEvent("on"+evenType,ary[i]); break; } } } ``` 但这样不同evenType都放在一起的,So我们最好`curEle["myBind"+evenType]` 然后我们需要这样解绑 ![](https://box.kancloud.cn/fe7af603494ed744e136b4b93b41378e_828x272.png) 解决重复问题:每一次自己再往自定义属性对一个的容器中添加前,看一下之前是否已经有了,有的话就不用再重新的添加了,同理也不需要往事件池中存储了 ![](https://box.kancloud.cn/6c2cad074aa3bf1b119c83564c149280_827x209.png) 解决顺序问题:我们不用浏览器自带的事件池了,而是自己模拟标准浏览器的事件池实现 on:创建事件池,并且把需要给当前元素绑定的方法依次增加到事件池中 on off ``` function on(curEle,eventType,evenFn){ if(!curEle["myEvent"+evenType]){ curEle["myEvent"+evenType] = []; } var ary = curEle["myEvent"+evenType]; for(var i=0;i<ary.length;++i){ var cur = ary[i]; if(cur === evenFn){ return; } } ary.push(evenFn); // curEle.addEventListener(evenType,run,false); //->执行on的时候,我们给当前元素绑定了一个点击的行为,当点击的时候执行run方法,run方法中的this是当前元素的curEle,并且浏览器给run传递了一个MouseEvent事件对象 bind(curEle,evenType,run); } function off(curEle,evenType,evenFn){ var ary = curEle["myEvent"+evenType]; for(var i=0;i<ary.length;++i){ ary.spice(ary[i],1); break; } } //->run: 我们只给当前元素的点击行为绑定一个方法run,当触发点击的时候执行的是run方法,我在run方法中根据自己存储的方法顺序分别的再把这些方法执行 function run(e){ e = e || window.event; var flag = e.target?true:false; //->IE6~8不兼容target,如果得到flag为false if(!flag){ e.target = e.srcElement; e.pageX = e.clientX + (document.documentElement.scrollLeft||document.body.scrollLeft); e.pageY = e.clientY + (document.documentElement.scrollTop||document.body.scrollTop); e.preventDefault = function(){ e.returnValue = false; }; e.stopPropagation = function(){ e.cancelBubble = true; } } //->获取自己事件池中国绑定的那些方法,并且让这些方法依次的执行 var ary = e.target["myEvent"+e.type]; for(var i=0;i<ary.length;++i){ var tempFn = ary[i]; tempFn.call(e.target,e); //this,e 也是一样的,this和e.target这里相等 } } ```