目录
六、e.preventDefault() 和 e.stopPropagation() 的区别
一、JS 事件机制
JS 的事件机制其实就是事件在 DOM 上的传递顺序,并且我们可以对应阶段对这些事件进行获取和处理。
可以看到 li 标签被 ul 标签包装了, 而 ul 标签又被 div 标签包装了。当我们点击 li 时,其实也点击了 ul 和 div。
<div>
<ul>
<li>l1</li>
<li>l2</li>
<li>l3</li>
<li>l4</li>
<li>l5</li>
</ul>
</div>
二、事件阶段划分
Javascript事件分为三个阶段:
- 捕获阶段
- 目标阶段
- 冒泡阶段
我们可以通过事件目标对象的 eventPhase
属性来得知当前事件在什么阶段。
eventPhase事件值:
点击代码黄色的right模块之后,可以发现:
捕获阶段是从外部一层层往内部传递至目标元素,eventPhase 的值为1,当发现是目标元素时,eventPhase 的值变为2。
冒泡阶段是从目标元素开始,一层层向外部传递至根节点,当当前是目标元素时,eventPhase 的值为2,若不是,则为3。
关键代码:
<script type="text/javascript">
var p = document.getElementById('p');
var child = document.getElementById("child")
var rgt = document.getElementById("right-container")
p.addEventListener('click', function (e) {
console.log('父节点捕获 e = ' + e.eventPhase );
}, true);
p.addEventListener('click', function (e) {
console.log('父节点冒泡 e = ' + e.eventPhase );
}, false);
child.addEventListener('click', function (e) {
console.log('子节点捕获 e = ' + e.eventPhase );
}, true);
child.addEventListener('click', function (e) {
console.log('子节点冒泡 e = ' + e.eventPhase );
}, false);
rgt.addEventListener('click', function (e) {
console.log('子rgt节点捕获 e = ' + e.eventPhase );
}, true);
rgt.addEventListener('click', function (e) {
console.log('子rgt节点冒泡 e = ' + e.eventPhase );
}, false);
</script>
三、注册事件
使用
addEventListener
监听事件,默认是监听冒泡阶段的事件,但可以通过useCapture
参数,来指定在什么阶段触发事件。若 useCapture为 true,则是在捕获阶段就触发事件。若 useCapture为 false,则是在冒泡阶段触发事件。
JQuery中,若使用 on 来监听事件,则是在捕获阶段触发事件。
p.addEventListener('click', function (e) {
console.log('捕获阶段监听 e = ' + e.eventPhase );
}, true);
p.addEventListener('click', function (e) {
console.log('冒泡阶段监听 e = ' + e.eventPhase );
}, false);
p.addEventListener('click', function (e) {
console.log('冒泡阶段监听 e = ' + e.eventPhase );
});
四、取消事件传递
我们可以通过 e.stopPropagation 阻止事件继续向上(冒泡)或向下(捕获)传递。
捕获阶段阻止事件:
若捕获阶段中,有元素监听捕获事件并阻止事件继续下传,则事件不会再下传到更内部的元素节点中。同时,这个被阻止的事件也不会再有冒泡阶段。
<div id="p">
parent
<div id="child">
child
<div id="right-container">right</div>
</div>
</div>
<script type="text/javascript">
var p = document.getElementById('p');
var child = document.getElementById("child")
var rgt = document.getElementById("right-container")
p.addEventListener('click', function (e) {
console.log('捕获阶段监听 e = ' + e.eventPhase );
e.stopPropagation(); // 阻止事件继续向下
}, true);
child.addEventListener('click', function (e) {
console.log('子节点捕获 e = ' + e.eventPhase );
}, true);
rgt.addEventListener('click', function (e) {
console.log('子rgt节点捕获 e = ' + e.eventPhase );
}, true);
</script>
冒泡阶段阻止事件:
在冒泡阶段中,有元素监听冒泡事件并阻止事件继续上浮,则事件不会再上浮到更外层的元素节点中。
<div id="p">
parent
<div id="child">
child
<div id="right-container">right</div>
</div>
</div>
<script type="text/javascript">
var p = document.getElementById('p');
var child = document.getElementById("child")
var rgt = document.getElementById("right-container")
p.addEventListener('click', function (e) {
console.log('捕获阶段监听 e = ' + e.eventPhase );
e.stopPropagation(); // 阻止事件继续向下
}, true);
child.addEventListener('click', function (e) {
console.log('子节点捕获 e = ' + e.eventPhase );
}, true);
rgt.addEventListener('click', function (e) {
console.log('子rgt节点捕获 e = ' + e.eventPhase );
}, true);
// 冒泡
p.addEventListener('click', function (e) {
console.log('冒泡阶段监听 e = ' + e.eventPhase );
e.stopPropagation(); // 阻止事件继续上浮
}, false);
child.addEventListener('click', function (e) {
console.log('子节点冒泡 e = ' + e.eventPhase );
}, false);
rgt.addEventListener('click', function (e) {
console.log('子rgt节点冒泡 e = ' + e.eventPhase );
}, false);
</script>
五、为什么要阻止事件传递?
阻止事件传递可以防止点透现象的发生。
点透现象:
当父子元素同时监听了同一种事件时(如click事件),由于事件机制,点击了子元素,父元素监听的事件也会触发。
<div id="div1">
<div id="div2"></div>
</div>
<script type="text/javascript">
var el1 = document.getElementById('div1')
var el2 = document.getElementById('div2')
el1.addEventListener('click', function(e) {
console.log('div1点击')
})
el2.addEventListener('click', function(e) {
console.log('div2点击')
})
</script>
处理点透现象:
只需要在div2的点击事件发生后,阻止事件继续传递
<div id="div1">
<div id="div2"></div>
</div>
<script type="text/javascript">
var el1 = document.getElementById('div1')
var el2 = document.getElementById('div2')
el1.addEventListener('click', function(e) {
console.log('div1点击')
})
el2.addEventListener('click', function(e) {
console.log('div2点击')
e.stopPropagation() // 阻止事件继续上浮
})
</script>
六、e.preventDefault() 和 e.stopPropagation() 的区别
e.preventDefault() 取消预设行为,阻止默认事件的发生,但不会阻止事件传递
e.stopPropagation() 阻止事件传递,阻止事件传递,但不会阻止默认事件的发生
看起来很绕,做一个小实验就能充分理解了:
阻止默认事件:
当我们使用 e.preventDefault() 阻止默认事件发生时:
阻止事件传递:
当我们使用 e.stopPropagation() 时,虽然冒泡事件被阻止了,但由于a标签的点击默认行为是跳转页面,页面还是被跳转过去了
实验代码:
<body>
<div id="p">
parent
<div id="child">
child
<a href="http://www.baidu.com" id="right-container">测试</a>
</div>
</div>
<script type="text/javascript">
var p = document.getElementById('p');
var child = document.getElementById("child")
var rgt = document.getElementById("right-container")
p.addEventListener('click', function (e) {
console.log('冒泡阶段监听 e = ' + e.eventPhase );
});
child.addEventListener('click', function (e) {
console.log('子节点冒泡 e = ' + e.eventPhase );
}, false);
rgt.addEventListener('click', function (e) {
e.preventDefault(); // 阻止默认事件
// e.stopPropagation(); // 阻止事件传递
console.log('子rgt节点冒泡 e = ' + e.eventPhase );
}, false);
</script>
</body>
七、事件解绑
监听了事件的元素,需要在适当时解除监听,这就叫事件解绑
当元素被绑定事件后,若没有在适当的时候解绑事件,可能会导致元素被重复绑定一样的事件,导致一次点击,触发两次。
如何解绑事件:
js: 绑定: addEventListener => 解绑: removeEventListener
jq: 绑定: on => 解绑: off
重复绑定的情况:
点击一次div1,div1的点击事件却会运行两次
<body>
<div id="div1">div1</div>
<script type="text/javascript">
var a = 1;
function Init() {
var el1 = document.getElementById('div1')
// 由于一个元素可以绑定多个不同的click事件,故解绑也要指定要解绑的事件
el1.addEventListener('click', addCount)
}
function addCount() {
a++
console.log('a = ' + a)
}
Init();
Init(); // 多绑定了一次
</script>
</body>
正常解绑的情况:
<body>
<div id="div1">div1</div>
<script type="text/javascript">
var a = 1;
function Init() {
var el1 = document.getElementById('div1')
el1.removeEventListener('click',addCount)
// 由于一个元素可以绑定多个不同的click事件,故解绑也要指定要解绑的事件
el1.addEventListener('click', addCount)
}
function addCount() {
a++
console.log('a = ' + a)
}
Init();
Init(); // 多绑定了一次
</script>
</body>
八、事件代理 (事件委托)
事件代理即将本来要绑定在子元素的事件,将其绑定到这个子元素的父元素身上,通过父元素的事件去处理触发的子元素。
假设有这样的情况:
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
...
...
... // 动态添加的
</div>
若要给li元素绑定点击事件,则要遍历整个li,给每个li元素绑定事件。
这样有两个弊端:
- 要for循环给子元素绑定事件,浪费效率
- 若li元素是动态的,则需要写更多代码处理
- 当事件要解绑时,需要重新遍历li元素,逐个解绑
使用事件代理
看起来就很麻烦吧。由于事件冒泡的从下至上的原则,子元素li的冒泡事件,可以被父元素监听到,我们可以将点击事件绑定到父元素身上进行统一处理:
- 父元素要监听子元素的触发事件
- 父元素要识别出这是哪一个子元素触发的事件
- 只需要给一个父元素绑定,无需遍历子元素逐个绑定
- 只需要解绑父元素一个,无需遍历子元素逐个解绑
- 子元素个数动态变化时,无需再专门处理
父元素如何识别是哪个子元素触发?
通过 e.target 或 e.srcElement (老版本IE) 识别到是哪一个子元素触发的
通过 nodeName 来区别是父元素触发的事件,还是子元素触发的事件,避免影响到父元素
实验代码:
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</div>
<script type="text/javascript">
var list = document.getElementById('list');
list.addEventListener('mouseover', function(e) {
var ev = e || window.event;
var childLi = ev.srcElement || ev.target;
console.log('mouseover ' + childLi.nodeName);
if(childLi.nodeName.toLowerCase() == 'li'){
childLi.style.background = 'green';
}
})
list.addEventListener('mouseout', function(e) {
var ev = e || window.event;
var childLi = ev.srcElement || ev.target;
console.log('mouseout ' + childLi.nodeName);
if(childLi.nodeName.toLowerCase() == 'li'){
childLi.style.background = 'white';
}
})
</script>
</body>
来源:CSDN
作者:lincya2
链接:https://blog.csdn.net/qq_34416331/article/details/103791225