事件作为DOM操作重要的一环,需要大家好好理解和运用,今天特意看了一下事件冒泡和事件代理的相关资料,感触颇深,也深感自己的无知不知道多浪费了多少内存,废话不多说进入正题:
1.事件冒泡:
通俗易懂的来讲,就是当一个子元素的事件被触发的时候(如onclick事件),该事件会从事件源(被点击的子元素)开始逐级向上传播,触发父级元素的点击事件。下面见详细的代码:
<div id="parent" style="background-color: #000;height: 400px;width: 400px" data-id="444">
<div id="child" style="background-color: #fff;height: 200px;width: 200px" data-id="555"></div>
</div>
document.getElementById('parent').οnclick=function () {
console.log(this.getAttribute('data-id'));
};
document.getElementById('child').οnclick=function () {
console.log(this.getAttribute('data-id'));
};
我们可以发现,当点击白色区域(子div)的时候,父级元素的click事件也被触发了。
看到这里不知有没有人跟我有同样的想法:
1.要是不给子元素添加具体的oncilck处理方法,也能冒泡么?
2.子元素触发的事件冒泡会触发父元素所有的事件么?还是触发对应的事件?
光说不练假把式,接下来就用代码探究一下:
1.
document.getElementById('parent').οnclick=function () {
console.log(this.getAttribute('data-id'));
};
// document.getElementById('child').οnclick=function () {
// console.log(this.getAttribute('data-id'));
// };
发现子元素在没有定义具体的click处理函数的时候仍然可以冒泡,触发父级元素的click事件。
2.
document.getElementById('parent').οnkeydοwn=function () {
console.log(this.getAttribute('data-id'));
};
document.getElementById('child').οnclick=function () {
console.log(this.getAttribute('data-id'));
};
我们发现只有相应的事件会发生事件冒泡,不相关的事件不受影响(注意由于click为鼠标的点击,所以同样会触发mousedown与mouseup等相关事件,同时发生冒泡!)
那么我们应该如何组织这讨厌的事件冒泡机制呢?
很简单,在事件触发时,会传入一个相应的event对象,其中有一个stopPropagation()方法可以阻止事件冒泡(IE中为cancleBubble=true),以下是详细代码:
document.getElementById('parent').οnclick=function () {
console.log(this.getAttribute('data-id'));
};
document.getElementById('child').οnclick=function (ev) {
var e = ev||window.event;//<span style="color:#FF0000;">IE中event可以通过window.event随时取到,而其他浏览器需要通过参数传递</span>
console.log(this.getAttribute('data-id'));
stopPropagation(e);
};
function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
}
可以通过运行结果来看,事件冒泡成功被阻止了。
阅读到这儿,朋友们有可能会觉得事件冒泡真是烦人,写个事件还要阻止冒泡!不过凡事都有双刃剑,事件冒泡同时给我们带来的还有事件委托这一减少DOM操作的神器。
2.事件委托
事件委托,首先按字面的意思就能看你出来,是将事件交由别人来执行,再联想到上面讲的事件冒泡,是不是想到了?对,就是将子元素的事件通过冒泡的形式交由父元素来执行。下面经过详细的例子来说明事件委托:
有可能在开发的时候会遇到这种情况:如导航每一个栏目都要加一个事件,你可能会通过遍历来给每个栏目添加事件:
<ul id="parentUl">
<li>我还是个孩子</li>
<li>我还是个孩子</li>
<li>我还是个孩子</li>
<li>我还是个孩子</li>
<li>我还是个孩子</li>
</ul>
var ul = document.getElementById('parentUl'),
li = ul.getElementsByTagName('li');
for (var i = 0; i<li.length;i++){
li[i].οnclick=function () {
alert(this.innerHTML);
}
}
这种方式来添加事件固然简单,但是需要多次操作DOM,如果有100、1000个同级的元素需要添加事件,这种方式简直不忍直视,
而且当我们动态添加新的Li元素的时候,新添加的Li元素是没有被绑定事件的。。。等等!我老王表示不服并给你抛出一段代码:
var ul = document.getElementById('parentUl'),
li = ul.getElementsByTagName('li');
function addClick() {
for (var i = 0; i<li.length;i++){
li[i].οnclick=function () {
alert(this.innerHTML);
}
}
}
addClick();
function addElement() {
var li = document.createElement('li');
li.innerHTML="我是新孩子";
ul.appendChild(li);
addClick();
}
addElement();
这样问题不就解决了么!根本不需要什么事件委托!(#傲娇脸)
这样确实解决了问题但是又增加了操作DOM的次数,大大降低了性能,让我们来看一下通过事件委托是怎样降低DOM操作次数的的:
var ul = document.getElementById('parentUl');
ul.οnclick=function (event) {
var e = event||window.event,
source = e.target || e.srcElement;//target表示在事件冒泡中触发事件的源元素,在IE中是srcElement
if(source.nodeName.toLowerCase() == "li"){ //判断只有li触发的才会输出内容
alert(source.innerHTML);
}
stopPropagation(e); //阻止继续冒泡
};
function addElement() {
var li = document.createElement('li');
li.innerHTML="我是新孩子";
ul.appendChild(li);
}
可以通过运行结果看见,新添加的子元素也可以成功通过事件委托显示内容,红色的为li中包含的span,由于代码中将源元素进行了过滤所以不会输出内容。事件委托不仅实现相同了功能,而且大大减少了DOM操作。有朋友可能会问,那我想要给每个li进行不同的处理呢?当然这个可以解决,我们可以通过ev.target获得源元素,就可以获取到data-id等属性或者自定义属性,并通过判断来进行不同的操作。