事件冒泡
IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)—-《JavaScript高级程序设计第四版》
举个例子:
#outer { width: 120px; height: 90px; background: pink; }
#father { margin-left: 10px; width: 100px; height: 60px; background: skyblue; }
#son { margin-left: 10px; width: 80px; height: 30px; background: tomato; }
<div id="outer">我是父亲 <div id="father">我是儿子 <div id="son">我是孙子</div> </div> </div>
let outer = document.getElementById('outer') let father = document.getElementById('father') let son = document.getElementById('son')
outer.addEventListener('click', function () { console.log('我是父亲') }) father.addEventListener('click', function () { console.log('我是儿子') }) son.addEventListener('click', function () { console.log('我是孙子') })
|
当我们点击 “我是孙子” 时,页面上会依次输出:我是孙子、我是儿子、我是父亲。在事件冒泡中 “我是孙子” 是最先点击的,所以也就最先触发了,然后在沿着 DOM 树向上层节点依次传递事件。然后就输出了刚刚的效果。
其实我们只给三个 div 设置了点击事件,让我们可以清晰的看到是以冒泡的形式来触发事件的,而在 DOM 节点中真实的样子是:我是孙子、我是儿子、我是父亲、body、html、Document。
注意:所有的现代浏览器都支持事件冒泡。IE5.5及早期版本会跳过元素(从
直接到 document)。
事件捕获
事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。—-《JavaScript高级程序设计第四版》
也就是前面事件冒泡的例子如果用的是事件捕获,那么点击 “我是孙子” 页面会依次输出:我是父亲、我是儿子、我是孙子。
而在 DOM 节点中真实的样子是:Document、html、body、我是父亲、我是儿子、我是孙子。
#outer { width: 120px; height: 90px; background: pink; }
#father { margin-left: 10px; width: 100px; height: 60px; background: skyblue; }
#son { margin-left: 10px; width: 80px; height: 30px; background: tomato; }
<div id="outer">我是父亲 <div id="father">我是儿子 <div id="son">我是孙子</div> </div> </div>
let outer = document.getElementById('outer') let father = document.getElementById('father') let son = document.getElementById('son')
outer.addEventListener('click', function () { console.log('我是父亲') }, true) father.addEventListener('click', function () { console.log('我是儿子') }, true) son.addEventListener('click', function () { console.log('我是孙子') }, true)
|
你也看到了,也就是在 addEventListener() 函数中配置一个布尔值,那加与不加有什么区别呢?就是使用冒泡事件还是捕获事件。
addEventListener() 函数默认是使用冒泡事件,为 false。如果想使用捕获事件,那么请将布尔值设置为 true。
如果想回顾一下这个函数,可以回顾MDN,或者看我后面的介绍。
事件代理
事件代理(事件委托):原理就是利用事件冒泡。只指定一个事件处理程序,就可以管理某一类型的所有事件。
我任务优点:
1.可以大量节省内存占用,减少事件注册。
2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适
缺点:事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。
最普遍做法点击li打印事件:
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName('li'); for(var i = 0;i < aLi.length;i++){ aLi[i].onclick = function(){ alert(123); } } } s
window.onload = function(){ var oUl = document.getElementById("ul1"); oUl.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert(123); alert(target.innerHTML); } } }
|
这里借用了 博主中文章的例子
<div id="box"> <input type="button" id="add" value="添加" /> <input type="button" id="remove" value="删除" /> <input type="button" id="move" value="移动" /> <input type="button" id="select" value="选择" /> </div>
window.onload = function(){ var Add = document.getElementById("add"); var Remove = document.getElementById("remove"); var Move = document.getElementById("move"); var Select = document.getElementById("select"); Add.onclick = function(){ alert('添加'); }; Remove.onclick = function(){ alert('删除'); }; Move.onclick = function(){ alert('移动'); }; Select.onclick = function(){ alert('选择'); } }
window.onload = function(){ var oBox = document.getElementById("box"); oBox.onclick = function (ev) { var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLocaleLowerCase() == 'input'){ switch(target.id){ case 'add' : alert('添加'); break; case 'remove' : alert('删除'); break; case 'move' : alert('移动'); break; case 'select' : alert('选择'); break; } } } }
|
点击新增元素节点
<input type="button" name="" id="btn" value="添加" /> <ul id="ul1"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul>
window.onload = function(){ var oBtn = document.getElementById("btn"); var oUl = document.getElementById("ul1"); var aLi = oUl.getElementsByTagName('li'); var num = 4; function mHover () { for(var i=0; i<aLi.length;i++){ aLi[i].onmouseover = function(){ this.style.background = 'red'; }; aLi[i].onmouseout = function(){ this.style.background = '#fff'; } } } mHover (); oBtn.onclick = function(){ num++; var oLi = document.createElement('li'); oLi.innerHTML = 111*num; oUl.appendChild(oLi); mHover (); }; }
window.onload = function(){ var oBtn = document.getElementById("btn"); var oUl = document.getElementById("ul1"); var aLi = oUl.getElementsByTagName('li'); var num = 4; oUl.onmouseover = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ target.style.background = "red"; } }; oUl.onmouseout = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ target.style.background = "#fff"; } }; oBtn.onclick = function(){ num++; var oLi = document.createElement('li'); oLi.innerHTML = 111*num; oUl.appendChild(oLi); }; }
|
给一个场景 ul > li > div > p,div占满li,p占满div,还是给ul绑定时间,需要判断点击的是不是li(假设li里面的结构是不固定的),那么e.target就可能是p,也有可能是div,这种情况你会怎么处理呢?
<ul id="test"> <li> <p>11111111111</p> </li> <li> <div> 22222222 </div> </li> <li> <span>3333333333</span> </li> <li>4444444</li> </ul>
var oUl = document.getElementById('test'); oUl.addEventListener('click',function(ev){ var target = ev.target; while(target !== oUl ){ if(target.tagName.toLowerCase() == 'li'){ console.log('li click~'); break; } target = target.parentNode; } })
|
addEventListener() 函数
addEventListener()和 removeEventListener() 给 DOM 节点添加或移除事件。共有三个参数:事件名、事件处理函
数和一个布尔值。true – 采用捕获事件,false(默认值)采用冒泡事件。
使用此函数可以设置多个事件。他们会按顺序执行。
例子可以看上面的 冒泡事件 和 捕获事件 的操作代码。当然这里也有很简单的使用。
let btn1 = document.getElementById("btn1"); let btn2 = document.getElementById("btn2");
btn1.addEventListener("click", () => { console.log('哈哈'); }, false);
btn2.addEventListener("click", () => { console.log('哈哈'); }, true);
|
注意:通过 addEventListener() 添加的事件处理程序只能使用 removeEventListener() 并传入与添加时同样的参数来移除。
let btn1 = document.getElementById("btn1"); let btn2 = document.getElementById("btn2");
btn1.addEventListener("click", function() { console.log('btn1'); }, false);
btn2.addEventListener("click", function() { console.log('btn2'); }, false);
btn1.removeEventListener("click", function() { console.log('btn1'); }, false);
btn2.removeEventListener("click", function() { console.log('这是无效移除,因为函数不同'); }, false);
|
一个很离谱的浏览器(你懂的)
此浏览器需要使用 attachEvent() 和 detachEvent() 来实现添加和移除事件。他们接收两个同样的参数:事件名字和事件处理函数。
注意:IE8 及更早版本只支持事件冒泡,所以使用 attachEvent() 添加的事件处理程序会添加到冒泡阶段。
var btn = document.getElementById("myBtn"); btn.attachEvent("onclick", function() { console.log("Clicked"); });
btn.attachEvent("onclick", function() { console.log("Hello world!"); });
|
同样的可以设置多个事件,但这里的执行顺序是反向触发,也就是先输出”Hello world!”,然后再输出”Clicked”。
注意:
attachEvent() 的第一个参数是”onclick”,而不是 DOM 的 addEventListener() 方法的”click”。
使用 attachEvent() 时,事件处理程序是在全局作用域中运行的,因此 this 等于 window。
兼容所有浏览器
function addHandler(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }
function removeHandler(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }
|