事件
定义:事件就是一件事情或者一个行为(对于元素来说,它的很多事件都是天生自带的),只要我们去操作这个元素,就会触发这些行为
事件就是元素天生自带的行为,我们操作元素,就会触发相关的事件行为
oBox.onclick=function(){};
元素天生自带的事件
鼠标事件
click
点击,PC端是点击,移动端的click代表单击
移动端使用click会有300MS延迟的问题,第一次点击后,如果在300MS内又点击了,浏览器认为其不是单击
dblclick
双击
mouseover
鼠标经过
mouseout
鼠标移出
mouseenter
鼠标进入
mouseleave
鼠标离开
mousemove
鼠标移动
mousedown
鼠标按下(鼠标左右键都起作用,它是按下即触发,click是按下抬起才会触发,而且是先把down和up触发,才会触发click)
mouseup
鼠标抬起
mousewheel
鼠标滚轮滚动,只要鼠标滚轮在滚动;而scroll是滚动条在滚动,让滚动条滚动有多种方式,可以用鼠标移动,也可以用滚轮移动,也可以用pagedown
键盘事件
keydown
键盘按下,ASCII码中对应的十进制的值(字母大写,其他如数字没有区别)
keyup
键盘抬起
keypress
和keydown类似,只不过keydown返回的是键盘码,keypress返回的是ASCII码值,ASCII码中对应的十进制的值(字母小写,其他如数字没有区别)
input
由于PC端有实体物理键盘,可以监听到键盘的按下和抬起,但是移动端是虚拟触控键盘,所以keydown,keyup在大部分手机上都没有,我们使用input事件统一代替他们(内容改变事件)
表单元素常用事件
focus
获取焦点
blur
失去焦点
change
内容改变
其它常用事件
load
加载完成
unload
用户离开页面
- 点击某个离开页面的链接 -在地址栏中输入了新的URL
- 使用前进或者后退按钮 -关闭浏览器 -重新加载页面
beforeunload
优先于unload事件,可以阻止onunload事件的执行
beforeunload事件在新窗口被打开的时候并不会执行,只在刷新和关闭时执行
scroll
滚动条滚动事件
resize
大小改变事件
window.onresize=function(){};
当浏览器窗口大小发生改变,会触发这个事件,执行对应的事情
移动端手指事件
单手指操作
touchstart
手指按下
touchmove
手指移开
touchend
手指离开
touchcancel
因为意外情况导致手指操作取消
多手指操作
gesturestart
多手指按下
gesturechange
手指改变
gestureend
手指离开
H5的AUTO/VIDEO音视频事件
canplay
可以播放,播放过程中可能出现由于资源没有加载完成导致的卡顿
canplaythrougth
资源加载完成,可以正常无障碍播放
页面(浏览器)事件
移动端和PC端都有
load
加载成功
error
加载失败
readystatechange
AJAX状态改变
scroll
滚动
resize
改变大小 window.onresize浏览器窗口大小改变触发
事件绑定
给元素天生自带的事件行为绑定方法,当事件触发,会把对应的方法执行
oBox.onclick=function(){};
DOM0级事件绑定
语法:[element].onxxx=function(){};
每一个元素对象都是对应类的实例,浏览器为其设置了很多私有属性和公有的属性方法,而onclick就是其中的一个私有属性(事件类私有属性,还有很多其它的事件私有属性),这些属性默认值是null
原理:就是给元素的某一个事件私有属性赋值(浏览器会建立监听机制,当我们触发元素的某个行为,浏览器会自己把属性中赋的值去执行)
DOM0事件绑定:只允许给当前元素的某个事件行为绑定一个方法,多次绑定,后面绑定的内容会替换前面绑定的,以最后一次绑定的方法为准
box.onclick=function(){
console.log(1);
}
box.onclick=function(){
console.log(2);
}
最后输出了2;
DOM2级事件绑定
增加语法:[element].addEventListener("xxx",function(){},false);
移除语法:[element].removeEventListener("xxx",function(){},false);addEventListener,removeEventListener这两个方法都是EventTarget.prototype上的方法
IE6~8增加:[element].attachEvent("onxxx",function(){});
IE6~8移除:[element].detachEvent("onxxx",function(){});
DOM2事件可以给当前元素的某一个事件行为绑定"
多个不同的方法"
function fn1(){console.log(1);}
function fn2(){console.log(2);}
function fn3(){console.log(3);}
function fn4(ev){
console.log(4,this===box,ev.target);
box.removeEventListener('click',fn5);
box.removeEventListener('click',fn8);
}
function fn5(){console.log(5);}
function fn6(){console.log(6);}
function fn7(){console.log(7);}
function fn8(){console.log(8);}
function fn9(){console.log(9);}
function fn10(){console.log(10);}
function fn11(){console.log(11);}
function fn12(){console.log(12);}
box.addEventListener('click',fn1);
box.addEventListener('click',fn3);
box.addEventListener('click',fn5);
box.addEventListener('click',fn7);
box.addEventListener('click',fn9);
box.addEventListener('click',fn2);
box.addEventListener('click',fn2);//=>重复
box.addEventListener('click',fn2);//=>重复
box.addEventListener('mouseenter',fn2);//=>增加到事件池中的
box.addEventListener('click',fn4);
box.addEventListener('click',fn6);
box.addEventListener('click',fn8);
box.addEventListener('click',fn10);
box.addEventListener('click',fn11);
box.addEventListener('click',fn12);
//=>输出结果
//2,1,3,5,7,9,2,4,6,10,11,12


DOM2事件要注意堆内存的问题
//=>变量提升阶段,fn1被声明加定义,地址指向第二个函数(console.log(2))的空间地址aaa,接下来JS代码自上而下执行,给box的click事件绑定了一个方法(该方法是addEventListener的一个私有变量,只是和全局中的指向同一个堆内存),方法的堆内存指向aaa,同时向浏览器的事件池中添加一个click事件的方法,再往下执行,执行removeEventListener方法,并把fn1也传给他作为私有变量,但是也是指向全局下的空间地址,执行时找到fn1的空间地址,把它从事件池中移除,此时全局下的fn1还是存在的,只是从事件池中移除了而已
function fn1(){
console.log(1);
}
function fn1(){
console.log(2);
}
box.addEventListener("click",fn1,false);
setInterval(function(){
box.removeEventListener("click",fn1,false);
},5000);
setTimeout(function () {
fn1();
},6000);
//=>页面加载完成5s内点击box都会执行fn1,5s后fn1从事件池内清除,但是全局下的fn1还是存在的,所以第6s还是会输出2
标注 VS IE低版本标准:addEventListener/removeEventListener
IE低版本:attachEvent/detachEvent
DOM0事件绑定和DOM2事件绑定的区别
-
1.机制不一样
- DOM0采用的是给私有属性赋值,所以只能绑定一个方法
- DOM2采用的是事件池机制,所以能绑定多个不同的方法
-
2.移除的操作
- DOM0级事件只要手动赋值为NULL即可移除,所以不必须要考虑绑定的是什么方法
- DOM2在移除的时候必须清楚要移除哪一个方法,才能在事件池中移除掉,所以基于DOM2做事件绑定,尽量不要绑定匿名函数,都绑定实名函数
box.onclick=function(){};
box.onclick=null;//=>赋值为null就移除了(所以不需要考虑绑定的是谁)
box.addEventListener("click",function(){console.log(1);});
box.removeEventListener("click",function(){
console.log(1);
})
//=>移除的并不是上面刚刚增加的方法,addEventListener和removeListener都是EventTarget原型上的方法,相当于调取原型上的方法并执行,所以括号里的相当于实参,那么就是这个函数的私有变量方法被执行的时候,fn指向一个堆内存,下面的被执行的时候,fn又指向另外一个堆内存
- 3.DOM2事件绑定中增加了一些DOM0无法操作的事件行为,例如:DOMContentLoaded事件(当页面中的HTML结构加载完成就会触发执行)
box.addEventListener("DOMCotentLoaded",fn);//=>可以
box.onDOMContentLoaded=fn;//=>不可以,box没有这个属性
--------------
window.onload=function(){
//=>当页面中的资源都加载完成(HTML结构加载完、CSS和JS等资源加载完成等)才会触发执行,而且只能执行一次
}
window.addEventListener("load",function(){});//=>这样处理就可以执行多次了
--------
jQuery中传递的selector是函数也是模拟了window.onload的方法,但是等到页面中的HTML结构加载完成就会执行,jQuery中使用的是DOM2事件,就是基于DOMContentLoad完成的,所以可以执行多次
else if ( jQuery.isFunction( selector ) ) {
return typeof rootjQuery.ready !== "undefined" ?
rootjQuery.ready( selector ) :
selector( jQuery );
}
rootjQuery=jQuery(document);//=>把document转换为JQ对象
所以$(function(){})相当于$(document).ready(function(){})
//=>原理:基于DOMContentLoaded完成的(IE中用的是onreadystatechange监听的,在document.readyState==="complete"的时候执行函数)
jQuery.ready.promise = function( obj ) {
if ( !readyList ) {
readyList = jQuery.Deferred();
if ( document.readyState === "complete" ) {//=>标准浏览器中的
setTimeout( jQuery.ready );
} else if ( document.addEventListener ) {
document.addEventListener( "DOMContentLoaded", completed, false );
window.addEventListener( "load", completed, false );
} else {//=>IE中的
document.attachEvent( "onreadystatechange", completed )
window.attachEvent( "onload", completed );
JQ中的事件绑定
on/off:基于DOM2事件绑定实现时间的绑定和移除(兼容所有的浏览器)
$(document).on("click",fn);
$(document).off("click",fn);
one:只绑定了一次,第一次执行完成后,会把绑定的方法移除(基于ON/OFF完成的,在on的事件的末尾加上移除这个事件)
function fn(){
$(document).off("click",fn);
}
$(document).on("click",fn);
click/mouseover/mouseout...:JQ提供快捷绑定方法,但是这些方法最后都是基于ON/OFF完成的
$(document).click(fn);
delegate:事件委托方法
$(document).delegate("#box","click",fn)
//=>把点击事件委托给document,不管点击文档中的哪一个元素,都会触发文档的点击行为,第一个参数代指事件源是#box我们执行fn这件事
bind/unbind:正常绑定
事件中低版本浏览器和标准浏览器的区别
-
1、:给事件绑定的方法被触发是低版本浏览器不会给事件传递事件对象,需要通过window.event获取,对应的事件对象上的一些属性的兼容问题
-
2、:冒泡传播的机制,标准浏览器是一直传播到window,IE高版本浏览器是传播到window,但是少了document,IE低版本浏览器一直传播到html为止
-
3、:DOM2事件的相关兼容问题:语法、this指向、重复问题、事件池顺序
事件对象
事件对象中记录了很多属性名和属性值(存储当前鼠标操作的信息),这些信息中包含了当前操作的基础信息,例如:鼠标点击位置的x/y轴坐标,鼠标点击的是谁(事件源)等信息
每一次事件触发浏览器传递进来的事件对象其实是一个实例,例如MouseEvent是MouseEvent这个类的实例
MouseEvent
鼠标事件对象
box.onclick=function(ev){
console.log(ev.target);
};
KeyboardEvent
键盘事件对象
button.onkeydown=function(ev){
console.log(ev.code);
};
Event
普通事件对象
window.onscroll=function(ev){
console.log(ev.preventDefault);
}
FocusEvent
表单事件对象
myInput.onfocus=function(ev){
console.log(ev.target);
};
WheelEvent
鼠标滚轮滚动对应的时间对象
myInput.onmousewheel=function(ev){
console.log(ev.target);//=>body
};
MouseEvent/wheelEvent中常用的属性
type
记录的是事件类型
path
记录的是事件冒泡传播的路径 [div#box,body,html,document,Window]
target
事件源(操作的是那个元素),MouseEvent实例上的私有属性
低版本浏览器中不存在target,所以低版本浏览器下,使用
srcElement
clientX/clientY
当前鼠标出发点距离当前可视窗口左上角的x/y轴轴坐标,MouseEvent实例上的私有属性
pageX/pageY
当前鼠标出发点距离BODY(第一屏幕)左上角的X/Y轴坐标,MouseEvent实例上的私有属性
低版本浏览器中不存在pageX/pageY,所以低版本浏览器下,使用scrollTop+clientY
preventDefault
阻止默认行为,不兼容IE6~8,Event这个类原型上的方法
低版本浏览器中不存在preventDefault,所以低版本浏览器下,使用returnValue=false
stopPropagation
阻止事件的冒泡传播,不兼容IE6~8,Event这个类原型上的方法
低版本浏览器中不存在stopPropagation,所以低版本浏览器下,使用cancelBubble=true
KeyboardEvent中常用的属性
code
当前按键"keyE"
key
当前按键"e"
which/keyCode
当前按键的ASCII编码
keyCode是用于兼容IE的
let code=ev.which||ev.keyCode;
常用键盘码
**`左-上-右-下`**:37-38-39-40
**`Backspace`**:8
**`Enter`**:13
**`Space`**:32
**`数字键`**:48~57
**`小写字母`**:65~90
`Delete`:46
`Shift`:16
`Alt`:18
`Ctrl`:17
`ESC`:27
事件的默认行为
事件本身就是天生就有的,某些事件触发,即使你没有绑定方法,也会存在一些效果,这些默认效果就是"事件的默认行为"
举例1--A标签
A标签的点击操作就存在默认行为
1、 页面跳转 2、锚点定位(HASH定位[哈希定位])
- 首先会在当前页面URL地址栏末尾设置一个HASH值,浏览器检测到HASH值后,会默认定位到当前页面中ID和HASH相同的盒子的位置(基于HASH值我们还可以实现SPA单页面应用)
举例2--input标签
1.输入内容可以呈现到文本框中
2.输入内容的时候会把之前输入的一些信息呈现出来(并不是所有浏览器和所有情况都有)
举例3--submit标签
1.点击按钮页面会刷新,在form中设置action属性,点击submit,会默认按照action指定的地址进行页面的跳转,并且把表单中的信息传递进去(非前后端分离中,由服务器进行页面渲染,由其它语言实现数据交互,一般都是这样处理)
<form action="http://www.zhufengpeixun.cn/">
<input type="submit" value="提交">
</form>
如何阻止默认行为
阻止A标签中的默认行为
在结构中阻止
<a href="javascript:;">阻止默认行为</a>;
javascript:void 0/undefined/null...;
//=>冒号和分号之间可以用其它占位
在JS中也可以阻止,给其click事件绑定方法,当我们点击A标签的时候,先触发click事件,其次才会执行自己的默认行为
link.onclick=function(ev){
ev=ev||window.event;
ev.preventDefault?ev.preventDefault():ev.returnValue=false;
}
-------------
link.onclick=function(ev){
ev=ev||window.event;
return false;//=>原理:点击A的时候,首先触发的是click,其次才会触发它的默认行为(我们在click中返回false,其意思是阻止下一步继续操作)
}
input text文本框内最多输入6位
myInput.onkeydown = function (ev) {
ev = ev || window.event;
let value = ev.target.value;
let reg = /^\s+|\s+$/g;
if (reg.test(value)) {
value = value.replace(reg, "");
}
let len = value.length;
if (len >= 6) {
let reg = /^(8|13|46|37|38|39|40)$/;
if (!reg.test(ev.which)) {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
}
}
};
事件的传播机制
冒泡传播
触发当前元素的某一个事件(点击事件)行为,不仅当前元素事件行为触发,而且其祖先元素的相关事件行为也会依次被触发,这就是"事件的冒泡传播机制"
事件传播的三个阶段
1、捕获阶段:点击inner的时候,首先会从最外层开始向内查找(找到操作的事件源),查找的目的是,构建出冒泡传播阶段需要传播的路线(查找是按照HTML层级结构找的)
2、目标阶段把事件源的相关操作行为触发(如果绑定了方法,则把方法执行)
3、冒泡传播按照捕获阶段规划的路线,自内而外,把当前事件源的祖先元素的相关事件行为依次触发(如果某个祖先元素事件行为绑定了方法,则把方法执行,没绑定方法,行为触发了,什么都不做,继续向上传播即可)
window.onclick = function () {
console.log('window');
};
document.onclick = function () {
console.log('document');
};
document.documentElement.onclick = function () {
console.log('html');
};
outer.onclick = function (ev) {
console.log('outer');//=>TRUE
};
inner.onclick = function (ev) {
console.log('inner');
};
//=>inner outer html document window

不同浏览器对于最外层祖先元素的定义是不一样的
- 谷歌:window->document->html->body
- IE高:window->html->body
- IE低:html->body...
1、事件对象是用来存储当前本次操作的相关信息,和操作有关,和元素无必然关联
2、当我们基于鼠标或者键盘等操作的时候,浏览器会把本次操作的信息存储起来(标准浏览器存储到默认的内存中(自己找不到),IE低版本存储到window.event中了),存储的值是一个对象(堆内存);操作肯定会触发元素的某个行为,也就会把绑定的方法执行,此时标准浏览器会把之前存储的对象(准确来说是堆内存地址),当做实参传递给每一个执行的方法,所以操作一次,即使再多方法中都有EV,但是存储的值都是一个(本次操作信息的对象而已)
//=>三个click事件用的是同一个事件对象
let aa = null;
document.body.onclick = function (ev) {
console.log('body', ev, ev === aa);//=>TRUE
};
outer.onclick = function (ev) {
console.log('outer', ev, ev === aa);//=>TRUE
};
inner.onclick = function (ev) {
ev = ev || window.event;
aa = ev;
};
想要阻止冒泡传播的话,可以通过事件对象的stopPropagation方法(Event原型上的)
document.body.onclick = function (ev) {
console.log("body");
};
outer.onclick = function (ev) {
ev = ev || window.event;
ev.stopPropagation ? ev.stopPropagation() : ev.cancelBubble = true;
console.log('outer', ev);
};
inner.onclick = function (ev) {
console.log('inner', ev);
};

onmouseenter和onmouseover的区别
1、over属于滑过(覆盖)事件,从父元素进入到子元素,属于离开了父元素,会触发父元素的out,触发子元素的over
2、enter属于进入,从父元素进入子元素,并不算离开父元素,不会触发父元素的leave,触发子元素的enter
3、enter和leave阻止了事件的冒泡传播,而over和out还存在冒泡传播的,所以对于父元素嵌套子元素这种情况,使用over会发生很多不愿意操作的事情,此时我们使用enter会更加简单,操作方便,所以真实项目中enter的使用会比over多
4、over和enter,over先触发;out和leave,out先触发

事件委托(事件代理)
利用事件的冒泡传播机制,如果容器的后代元素中,很多元素的点击行为(其它事件行为也是)都要做一些处理,此时我们不需要再像以前那样一个一个获取一个一个的绑定了,我们只需要给容器的click绑定方法即可,这样不管点击的是哪个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而做不同的事情

发布订阅设计模式(观察者模式)
准备一个容器,把到达指定时候要处理的事情,首先一一的增加到容器中(发布计划并且向计划表中订阅方法),当到达指定时间点,通知容器中的方法依次执行即可
发布订阅设计模式源码
(function(){
class Subscribe{
constructor(){
this.pond=[];
}
add(fn){
let pond=this.pond,
isExist=false;//=>代表fn在pond中不存在,如果不存在就加入pond中
for(let i=0;i<pond.length;i++){//=>那fn和pond中的每一项进行比较,如果有相等的项,证明fn在pond中已经存在了,那么让isExist变成true就不用往里面存了
let item=pond[i];
item===fn?isExist=true:null;
if(isExist){
return;
}
}
!isExist?pond.push(fn):null;
}
remove(fn){
let pond=this.pond;
for(let i=0;i<pond.length;i++){
let item=pond[i];
item===fn?pond[i]=null:null;//=>移除的时候赋值为null而不是直接删除掉,因为如果执行容器的时候,假如同时操作了remove函数,那会导致数组的索引发生改变
}
}
fire(...arg){
let pond=this.pond;
for(let i=o;i<pond.length;i++){
if(item===null){//=>把从容器中移除的事件去除掉,而且不执行
pond.splice(i,1);
i--;
continues;
}
item(...arg);
}
}
}
window.Subscribe=Subscribe;
})()
jQuery中提供的发布订阅设计模式
let $plan=$.Callbacks();//=>创建一个空的计划表:空容器
setTimeout(function(){
$plan.fire(10,20);//=>fire就是通知容器中的方法按照顺序依次执行的:10和20是执行容器中每一个方法的时候,都给他们传递两个参数值
},1000);
function fn(){};
$plan.add((x,y)=>{//ADD是向容器中增加方法
console.log(1);
});
$plan.add(fn);
$plan.remove(fn);//=>remove是从容器中移除方法


Function原型上bind的原理
执行bind方法,会形成一个不销毁的私有栈内存(AAA),返回一个
新的匿名函数(每一次返回的都不不一样,不是相同的堆内存)
Function.prototype.myBind=function myBind(context,...arg){
//=>this:当前调取bind方法的函数
//=>context:把调取方法的函数中的this改为context
//=>arg:需要传递给调取方法的函数的实参
//=>以上这些都是执行bind方法中的私有变量
let _this=this;
return function anonymous(...innerArg){
//=>inneArg可能有值,可能没有值,如果把bind的返回结果给元素的时间绑定,则事件触发,执行这个匿名函数,会把事件对象传递进来ev
_this.apply(context,arg.concat(innerArg));
console.log(this);//=>如果绑定了事件,这里的this指的是绑定事件的元素
}
}
function fn(){
console.log(1);
}
let obj={};
box1.onclick=fn.myBind(obj,10);//=>bind方法中的this指的是fn
相当于 box1.onclick=function anonymous(ev){
fn.apply(obj,[10,ev]);
//=>匿名函数中的this指的是box1
}
//=>当我们触发box的点击行为,执行的是返回的匿名函数
