由网上文章整理成,具体可以参考下面文章:
https://www.cnblogs.com/xiaohuochai/p/5859476.html
https://segmentfault.com/a/1190000013176643
https://segmentfault.com/a/1190000012729080
https://www.cnblogs.com/christineqing/p/7607113.html
https://segmentfault.com/a/1190000005654451#articleHeader8
https://www.cnblogs.com/lihongfei0602/p/4062345.html
https://www.cnblogs.com/chunlei36/p/6354167.html
javascript操作CSS称为脚本化CSS,而javascript与HTML的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,而事件流(又叫事件传播)描述的是从页面中接收事件的顺序。
1.事件冒泡
IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。所有现代浏览器都支持事件冒泡,并且会将事件一直冒泡到window对象。
2.事件捕获
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前就捕获它。IE9+、Safari、Chrome、Opera和Firefox支持,且从window开始捕获(尽管DOM2级事件规范要求从document)。由于老版本浏览器不支持,所以很少有人使用事件捕获。
冒泡还是捕获?
对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。
3.DOM事件流
事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:事件捕获阶段(capture phase)、处于目标阶段(target phase)和事件冒泡阶段(bubbling phase)。
首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
DOM事件流:同时支持两种事件模型:捕获型事件和冒泡型事件,但是,捕获型事件先发生。两种事件流会触及DOM中的所有对象,从document对象开始,也在document对象结束。
W3c明智的在这场争斗中选择了一个择中的方案。任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段。
你可以选择是在捕获阶段还是冒泡阶段绑定事件处理函数,这是通过addEventListener()方法实现的,如果这个函数的最后一个参数是true,则在捕获阶段绑定函数,反之false(默认),在冒泡阶段绑定函数。
4.一个十分有趣的例子
具体可以参考:https://segmentfault.com/a/1190000012729080
#a{
width: 300px;
height: 300px;
background: pink;
}
#b{
width: 200px;
height: 200px;
background: blue;
}
#c{
width: 100px;
height: 100px;
background: yellow;
}
var a = document.getElementById("a"),
b = document.getElementById("b"),
c = document.getElementById("c");
c.addEventListener("click", function (event) {
console.log("c1");
// 注意第三个参数没有传进 false , 因为默认传进来的是 false
//,代表冒泡阶段调用,个人认为处于目标阶段也会调用的
});
c.addEventListener("click", function (event) {
console.log("c2");
}, true);
b.addEventListener("click", function (event) {
console.log("b");
}, true);
a.addEventListener("click", function (event) {
console.log("a1");
}, true);
a.addEventListener("click", function (event) {
console.log("a2")
});
a.addEventListener("click", function (event) {
console.log("a3");
event.stopImmediatePropagation();
}, true);
a.addEventListener("click", function (event) {
console.log("a4");
}, true);
整个的html页面就是下面这三个小盒子。
那么现在有三个问题:
1.如果点击c或者b,输出什么?(答案是a1、a3)
stopImmediatePropagation包含了stopPropagation的功能,即阻止事件传播(捕获或冒泡),但同时也阻止该元素上后来绑定的事件处理程序被调用,所以不输出 a4。因为事件捕获被拦截了,自然不会触发 b、c 上的事件,所以不输出 b、c1、c2,冒泡更谈不上了,所以不输出 a2。
2.如果点击a,输出什么?(答案是 a1、a2、a3)
不应该是 a1、a3、a2 吗?有同学就会说:“a1、a3可是在捕获阶段被调用的处理程序的,a2 是在冒泡阶段被调用的啊。”这正是要说明的:虽然这三个事件处理程序注册时指定了true、false,但现在事件流是处于目标阶段,不是冒泡阶段、也不是捕获阶段,事件处理程序被调用的顺序是注册的顺序。不论你指定的是true还是false。换句话来说就是现在点击的是a这个盒子本身,它处于事件流的目标状态,而既非冒泡,又非捕获。(需要注意的是,此时的eventPhase为2,说明事件流处于目标阶段。当点击a的时候,先从document捕获,然后一步步往下找,找到a这个元素的时候,此时的target和currentTarget是一致的,所以认定到底了,不需要再捕获了,此时就按顺序执行已经预定的事件处理函数,执行完毕后再继续往上冒泡…)
3.如果注释掉event.stopImmediatePropagation,点击c,会输出什么?(答案是 a1、a3、a4、b、c1、c2、a2)
如果同一个事件处理程序(指针相同,比如用 handler 保存的事件处理程序),用 addEventListener或 attachEvent绑定多次,如果第三个参数是相同的话,也只会被调用一次。当然,如果第三个参数一个设置为true,另一个设置为false,那么会被调用两次。
而在这里,都是给监听函数的回调赋予了一个匿名函数,所以其实每个处理函数都会被调用。需要注意的是,如果你还不明白为什么在c上触发的先是c1再是c2的话,那么你就需要在去看看第二个问题锁描述的内容了。
5.事件冒泡与事件捕获应用:事件代理
举个例子。 ul 下面很多 li, 点击li,输出 li 的文字。循环每个li 添加点击事件不是很好的选择。 可以把点击事件写在 ul 上,点击 li ,事件会冒泡到 ul, 这样只用写一个事件。
具体可以参考:
https://segmentfault.com/a/1190000005654451#articleHeader8
https://www.cnblogs.com/liugang-vip/p/5616484.html
6.事件委托
事件委托顾名思义:将事件委托给另外的元素。其实就是利用DOM的事件冒泡原理,将事件绑定到目标元素的父节点。
如果要为大量的元素节点绑定事件,完美可以用事件委托完美解决,直接将事件绑定在这些元素的父节点上,只需要绑定一次,就可以在所有子节点触发事件。
最适合使用事件委托技术的事件包括click、mousedown、mouseup、keydown、keyup和keypress。
具体可以参考:https://www.cnblogs.com/lihongfei0602/p/4062345.html
事件代理就是事件委托,具体解释可以参考:
https://www.cnblogs.com/liugang-vip/p/5616484.html
https://www.cnblogs.com/xiayu25/p/6269652.html
7.事件处理程序:HTML事件处理、DOM 0级、DOM2级、DOM3级
主要参考:
https://segmentfault.com/a/1190000007082623
https://www.cnblogs.com/xiaohuochai/p/5859674.html
https://segmentfault.com/a/1190000012022432
事件处理程序又叫事件侦听器,实际上就是事件的绑定函数。事件发生时会执行函数中相应代码。事件处理程序有HTML事件处理程序、DOM0级事件处理程序、DOM2级事件处理程序和IE事件处理程序四类。
有人可能会问,为什么没有DOM1级事件处理呢?因为1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。
0. HTML事件处理
在了解DOM0级事件之前,我们有必要先了解下HTML事件处理程序,也是最早的这一种的事件处理方式,代码如下:
//或者
以上代码我们通过直接在HTML代码里定义了一个onclick的属性触发showFn方法,这样的事件处理程序最大的缺点就是HTML于JS强耦合,我们一旦需要修改函数名就得修改两个地方。当然其优点是不需要操作DOM来完成事件的绑定。
HTML事件处理程序会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。通过event变量,可以直接访问事件对象,不用自己定义它,也不用从函数的参数列表中获取
在事件处理程序函数内部,可以像访问局部变量一样访问document及该元素本身的成员。如此一来,事件处理程序要访问自己的属性就简单多了
下列这种情况输出的是空字符串’’:
1.DOM 0级事件
那么什么是DOM0级处理事件呢?DOM0级事件就是将一个函数赋值给一个事件处理属性,比如:
比较HTML事件处理程序,DOM 0级对HTML和JS代码进行了解耦,但是DOM 0级只能绑定一个处理程序。
以上代码我们给button定义了一个id,通过JS获取到了这个id的按钮,并将一个函数赋值给了一个事件处理属性onclick,这样的方法便是DOM0级处理事件的体现。我们可以通过给事件处理属性赋值null来解绑事件。
DOM0级事件处理程序的缺点在于一个处理程序无法同时绑定多个处理函数,比如我还想在按钮点击事件上加上另外一个函数。
2.DOM 2级事件
DOM2级事件在DOM0级事件的基础上弥补了一个处理程序无法同时绑定多个处理函数的缺点,允许给一个处理程序添加多个处理函数。代码如下:
DOM2级事件定义了addEventListener和removeEventListener两个方法,分别用来绑定和解绑事件,方法中包含3个参数,分别是绑定的事件处理属性名称(不包含on)、处理函数和是否在捕获时执行事件处理函数。如果我们还需要添加一个鼠标移入的方法,只需要:
btn.addEventListener('mouseover', showFn, false);
这样点击按钮和鼠标移入时都将触发showFn方法。
需要注意的是IE8级以下版本不支持addEventListener和removeEventListener,需要用attachEvent和detachEvent来实现:
btn.attachEvent('onclick', showFn); // 绑定事件
btn.detachEvent('onclick', showFn); // 解绑事件
这里我们不需要传入第三个参数,因为IE8级以下版本只支持冒泡型事件。
3.DOM 3级事件
DOM3级事件在DOM2级事件的基础上添加了更多的事件类型,全部类型如下:
1.UI事件,当用户与页面上的元素交互时触发,如:load、scroll
2.焦点事件,当元素获得或失去焦点时触发,如:blur、focus
3.鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
4.滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
5.文本事件,当在文档中输入文本时触发,如:textInput
6.键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
7.合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
8.变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
同时DOM3级事件也允许使用者自定义一些事件。
总结:
1.如果同时出现HTML事件处理程序和DOM0级事件处理程序,DOM0级会覆盖HTML事件处理程序;
2.chrome/opera/safari等webkit内核的浏览器会按照事件处理程序出现的顺序来排列,所以结果为:DOM2级 DOM0级
3.firefox浏览器和IE浏览器会将DOM0级事件优先调用、所以firefox和IE11浏览器结果为:DOM0级 DOM2级
4.IE9、10浏览器结果为:DOM0级 DOM2级 IE
5.IE8-浏览器结果为:DOM0级 IE
8.事件对象(Event)
主要参考文章:
https://www.cnblogs.com/xiaohuochai/p/5862775.html
https://segmentfault.com/a/1190000007147895
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息(Event对象上有关于事件的属性和方法)。所有浏览器都支持event对象,但支持方式不同。
//IE8-浏览器输出undefined,其他浏览器则输出事件对象[object MouseEvent]
//firefox浏览器输出undefined,其他浏览器则输出事件对象[object MouseEvent]
//于是,对于获取事件对象的常见兼容写法如下
属性和方法
事件对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有些共有的属性和方法。
Event事件对象方法:
stopPropagation、preventDefault、stopImmediatePropagation
Event对象属性:
1.type属性(通过type我们可以获取事件发生的类型,比如点击事件我们获取的是’click’字符串。)
2.target属性(target属性主要用于获取事件的目标对象,比如我们点击a标签获取的是a标签的html对象。)
3.鼠标事件属性(在用鼠标触发事件时,主要的事件属性包含鼠标的位置和按键的状态,比如:clientX和clientY指定了鼠标在窗口坐标中的位置,button和which指定了按下的鼠标键是哪个。)
4.键盘事件属性(在用键盘触发事件时,主要的事件属性包含键盘的按键keyCode和是否按下特殊键,比如:keyCode指定了按下键的键码值,ctrlKey指定是否按下了ctrl键。)
类似的事件属性还有表单事件属性和window事件属性等,这里不再做详细介绍。