移动端_Day3
一、事件深入
1.1、事件流
比如说有一个父子盒模型,同时都有点击事件。当你点击了子元素元素,这个点击事件不仅仅发生在子元素的身上,也发生在父元素的身上,此时的整个点击事件的经过,就是一个事件流。
<script> var div = document.querySelector("div"); var p = document.querySelector("p"); p.onclick = function(){ alert("我是子盒子"); }; div.onclick = function(){ alert("我是父盒子"); }; document.body.onclick = function(){ alert("我是body标签") };
document.odocumentElement.onclick = function(){ alert("我是html标签"); };
document.onclick = function(){ alert("我是document标签"); };
window.onclick = function(){ alert("我是window标签"); };
</script>
从子盒子向外冒泡触发事件。
我们为了描述事件流的执行顺序,特意分为了两个阶段:捕获阶段、冒泡阶段
1.2、DOM0级别事件绑定。
DOM的级别分为DOM0级、1级、2级、3级,每个级别是不同的标准,标准一直在升级
之前学习的事件绑定类型
oDiv.onclick=function(){}
之前写的是注册方法,这个是DOM0级事件。就是把onclick当做属性添加给了oDiv元素。
执行过程分别是子元素→父元素→body→html(documentElement)→document→window
通过实验我们发现,DOM0级事件绑定方法,只监听的是冒泡阶段,事件的捕获阶段没有监听
在IE9和Chrome中,事件会冒泡到window对象,而IE6,7,8仅仅会冒泡到document对象。
****************自定义函数属性在DOM中的写法*************************
<script> var box1 = document.querySelector(".box1"); box1.onmouseenter = changeRed; box1.onmouseleave = changeBlue; function changeRed(){ this.style.backgroundColor="red"; }; function changeBlue(){ this.style.backgroundColor="skyblue"; }; </script>
使用DOM0级触发的事件监听,this指的就是当前被触发的元素本身。
<div class="box1" onclick="alert('我是box1')"> <div class="box2" onclick="alert('我是box2')"> <div class="box3" onclick="alert('我是box3')"> </div> </div> </div>
DOM0级还有一种写法, 就是将事件写到了标签上,同样的只监听冒泡阶段(这种写法不建议)
box.onclick = function() { alert("触发2") } box.onclick = function() { alert("触发1") }
DOM0级事件中,同一个元素如果设置了两个同样的事件监听,后写的会覆盖先写的执行(并不会累加执行)
box.onmouseenter = function() { alert("触发1") } box.onmouseover = function() { alert("触发2") }
因为onmouseover的事件优先级高,所以会先执行“触发2”然后紧接着执行“触发1”
1.3、DOM2级事件
DOM1级规范中,没有对事件进行改动
DOM3级事件中,主要是html新增的api和自定义事件,以及触发事件等等,这些都不是我们移动端课程的主要内容,也不是事件流的主体
DOM2级事件做了新的规范,不用on***来监听事件了,而是增加了一个方法
addEventListener()
add增加
Event事件
Listener监听
它一共接受3个参数,分别是事件内容(加双引号)、函数主体、是否监听捕获阶段。不同的参数之间用逗号隔开。
第一个参数:事件名称,注意不用写on,比如click,mouseenter等,注意要用双引号
第二个参数:可以是匿名函数,也可以是实名函数
第三个参数:false:监听冒泡阶段 true:监听捕获阶段。
移除事件用removeEventListener();
<script> var div = document.querySelector("div"); var p = document.querySelector("p"); p.addEventListener("click",function(){ alert("我是字元素,捕获阶段"); },true); div.addEventListener("click",function(){ alert("我是父元素,捕获阶段") },true); document.body.addEventListener("click",function(){ alert("我是body元素,捕获阶段") },true); document.documentElement.addEventListener("click",function(){ alert("我是html元素,捕获阶段") },true); document.addEventListener("click",function(){ alert("我是document元素,捕获阶段") },true); window.addEventListener("click",function(){ alert("我是window元素,捕获阶段") },true); p.addEventListener("click",function(){ alert("我是子元素,冒泡阶段") },false); div.addEventListener("click",function(){ alert("我是父元素,冒泡阶段") },false); document.body.addEventListener("click",function(){ alert("我是body元素,冒泡阶段") },false); document.documentElement.addEventListener("click",function(){ alert("我是html元素,冒泡阶段") },false); document.addEventListener("click",function(){ alert("我是document元素,冒泡阶段") },false); window.addEventListener("click",function(){ alert("我是window元素,冒泡阶段") },false); </script>
l 需要注意的是如果被触发的元素是自己,不区分捕获或者冒泡阶段,只会按照书写顺序执行
比如说只有一个元素绑定事件,点击之后是按书写顺序执行。
当事件触发在目标阶段时,会根据事件注册的先后顺序执行,在其他两个阶段注册顺序不影响事件执行顺序。也就是说如果该处既注册了冒泡事件,也注册了捕获事件,则按照注册顺序执行。
<script> var div = document.querySelector("div"); var p = document.querySelector("p"); p.addEventListener("click",function(){ alert("p,冒泡") },false); p.addEventListener("click",function(){ alert("p,捕获") },true); div.addEventListener("click",function(){ alert("div,冒泡") },false); div.addEventListener("click",function(){ alert("div,捕获") },true); </script>
若点击子盒子,则此处的运行顺序应该是div捕获,p冒泡,p捕获,div冒泡,是因为该处既注册了冒泡事件,也注册了捕获事件,则按照注册顺序执行。
若使用谷歌浏览器则还是按照捕获-冒泡的顺序,若使用IE浏览器才会出现上述结果,是由于浏览器的兼容性问题。
DOM2级中this的指向问题
和DOM0级一样,也是指向触发元素本身。
当我们给一个元素绑定两个相同的事件监听时,会按照书写顺序执行两次,不会像DOM0级那样覆盖。
box.addEventListener('click', function() { alert("触发2") }, false) box.addEventListener('click', function() { alert("触发1") }, false)
如果设置同一个元素不同的事件,和DOM0级一样会按照事件的优先级顺序来执行。
1.4、低版本IE浏览器的事件绑定
低版本IE一直有兼容问题,我们的事件绑定也不例外
IE6,7,8不支持addEventListener()方法,用attachEvent()和deattachEvent()方法来添加或者移除事件,只有两个参数,一个是添加的参数(注意加on),一个是函数。
nP.attachEvent("onclick", function() { })
第一个参数必须写on,和addEventListener不一样
第二个参数就是事件函数
没有第三个参数,因为只能监听冒泡阶段,所以写法和on***一样
低版本IE浏览器的this指向问题。
我们会发现低版本IE的this指向的是window。
function changeRed() { alert(this == window) }
接下来讨论同样的元素设置同样的事件。
box.attachEvent('onmouseenter', function() { alert("触发3") }) box.attachEvent('onmouseenter', function() { alert("触发2") }) box.attachEvent('onmouseover', function() { alert("触发1") })
也会分别执行,但是会倒置执行,分别是触发1,触发2,触发3.
接下来讨论同样元素设置不同事件。
如果设置同一个元素不同的事件,和DOM0级一样会按照事件的优先级顺序来执行。
1.5,事件轮子。
我们需要封装一个方法,兼容高版本和低版本浏览器,主要是低版本的attachEvent和高版本addEventListener两个方法的兼容
<script> //声明一个函数addEvent,有三个参数 // 第一个参数obj指定要绑定事件的元素 // 第二个参数eventtype指的是事件名称,在这里不加on,需要加on的时候再用字符串的拼接 // 第三个参数就是处理函数fn function addEvent(obj,eventtype,fn){ // 判断该元素所在的浏览器支持哪一种事件绑定的方法 if(obj.addEventListener){ // 如果该元素所在的浏览器支持addEventListener obj.addEventListener(eventtype,fn,false); }else if(obj.attachEvent){ // 如果该元素所在的浏览器支持attachEvent的方法 // 注意此时需要加"on"+eventtype obj.attachEvent("on"+eventtype,function(){ // 因为低版本浏览器的this是指向window的,所以需要单独设置属性让它指向该元素 // call指的是将函数上下文this指向call里面的元素 fn.call(obj); }) }else{ // obj['key']此时key代表的是obj的一个属性名,需要加上''使用,obj['key']———取obj的key属性的值 obj["on"+eventtype] = fn; } } var div = document.querySelector("div"); addEvent(div,"click",function(){ this.style.backgroundColor = "red"; }) </script>
我们把所有之前学习的事件流进行一个总结
事件流是先下后上,先捕获后冒泡。但是不同的添加监听的方式,决定了能监听的那一部分
- DOM0级,只能监听冒泡阶段。不能有同名事件,否则会覆盖。this指向的是被触发的内个元素。高版本浏览器会冒泡到window,但是IE8及以下会冒泡到document
- DOM2级,addEventListener,可以自由设置捕获和冒泡,第三个参数如果是false则代表冒泡,true代表捕获。事件名不加on,如果有同名事件,不会覆盖会依次执行。this指的是触发事件的元素。会冒泡到window
- IE6,7,8使用的是自己的方法attachEvent,只能监听冒泡阶段。没有第三个参数。事件名需要写on,可以有同名事件,但是会反向执行。this指向的是window,冒泡只冒泡到document阶段
二、事件委托 target属性
什么是事件委托?当页面中很多个同样 的盒子都添加了事件监听,此时如果按照我们传统的想法,就是给所有的盒子都添加事件监听
<script> // 事件委托,如果给所有的div添加监听,此时利用他们的父亲body委托执行 document.body.onclick = function(event){ // event.target指的是被触发事件的目标对象,所以下面的代码指的就是被触发事件的目标对象(元素)样式的背景颜色为红色 event.target.style.background = "red"; } </script>
target指的就是event事件对象的target属性,属性值指的就是被触发的目标对象
事件委托的原理其实就是利用事件冒泡,将事件冒泡给祖先元素https://www.webhek.com/post/event-delegate.html
<script> var btn = document.querySelector(".btn"); var box = document.querySelector(".box"); btn.onclick = function(){ //用JS动态创建元素的方法 var oP = document.createElement("p"); //将节点加到节点树里面 box.appendChild(oP); } //如果此时要动态的将某一个不确定的盒子的样式改变,则通过传统方法很难以捕捉到目标盒子,这时就可以用事件委托 box.onclick = function(event){ event.target.style.background = "green"; } </script>
下面介绍低版本IE的兼容性问题,在低版本ie中不支持target属性,所以需要使用兼容性写法。
document.body.onclick = function(event) { window.event.srcElement.style.backgroundColor = 'orange' }
兼容写法如下:
// 事件委托,如果给所有的div添加监听,此时利用他们的父亲body委托执行 document.body.onclick = function(event) { // 能力判断,如果有event对象,就获取targe属性,没有就获取window对象的SrcElement属性 var event = event || window.event; var ele = event.target || event.srcElement; ele.style.backgroundColor = 'orange' }
三、阻止事件传播
我们在前面的课程中学习了,事件流,事件会有捕获和冒泡,此时我们希望阻止事件传播
有一个父子盒子nDiv和nP,此时正常如果都设置了点击事件,会出现事件冒泡,所以此时我们给子盒子设置 event.stopPropagation()方法,会阻止事件冒泡
nP.onclick = function(event) { // stopPropagation方式是event实际中阻止事件流传播的方法 event.stopPropagation() this.style.background = 'pink' } nDiv.onclick = function() { alert("我是父元素") }
最后只会让子盒子nP的颜色变为粉色,父盒子不会弹出alert
DOM0级和DOM2级事件都是同样的方法组件事件传播,详情见案例
注意IE低版本浏览器的兼容性问题。
nP.onclick = function(event) { // 低版本ie8以下兼容的组织事件冒泡的写法 window.event.cancelBubble = true; this.style.background = 'pink' }
兼容写法如下:
nP.onclick = function(event) { // 短路语法进行兼容 var event = event || window.event; if (event.stopPropagation) { // 高版本浏览器 event.stopPropagation() } else { // 兼容ie8以下的低版本 event.cancelBubble = true; } this.style.backgroundColor = "blue" }