事件流
如果单击了页面中的某个按钮,同时也单击了按钮的容器元素,甚至单击了整个页面。
IE提出的是冒泡流,网景提出的是捕获流。
<div id="content">content <div id="btn">button</div> </div> <script type="text/javascript"> var content = document.getElementById("content"); var btn = document.getElementById('btn'); btn.onclick = function(){ alert("btn"); }; content.onclick = function(){ alert("content"); }; document.onclick = function(){ alert("document"); } </script> //点击容器 #btn ,则弹出的顺序是:btn > content > document //点击容器 #content,则弹出的顺序是:content > document //点击容器 document,则弹出的是 document
JS事件流原理图 :
1、一个完整的事件流是从 window 开始,最后回到 window 的一个过程
2、事件流被分为三个阶段:(1-5)捕获过程、(5-6)目标过程、(6-10)冒泡过程
<div id="wrapDiv">wrapDiv <p id="innerP">innerP <span id="textSpan">textSpan</span> </p> </div> <script> var wrapDiv = document.getElementById("wrapDiv"); var innerP = document.getElementById("innerP"); var textSpan = document.getElementById("textSpan"); // 捕获阶段绑定事件 window.addEventListener("click", function(e){ console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.addEventListener("click", function(e){ console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.documentElement.addEventListener("click", function(e){ console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.body.addEventListener("click", function(e){ console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); wrapDiv.addEventListener("click", function(e){ console.log("wrapDiv 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); innerP.addEventListener("click", function(e){ console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); textSpan.addEventListener("click", function(e){ console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); // 冒泡阶段绑定的事件 window.addEventListener("click", function(e){ console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.addEventListener("click", function(e){ console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.documentElement.addEventListener("click", function(e){ console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.body.addEventListener("click", function(e){ console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); wrapDiv.addEventListener("click", function(e){ console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); innerP.addEventListener("click", function(e){ console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); textSpan.addEventListener("click", function(e){ console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); </script>
如果点击textSpan元素,控制台打印如下图:
如上图所示,事件传播的过程是先捕获,再冒泡。
那么,如果不使用addEventListener方法绑定的事件(如onclick),会发生在哪个阶段?
<div id="wrapDiv">wrapDiv <p id="innerP">innerP <span id="textSpan">textSpan</span> </p> </div> <script> var wrapDiv = document.getElementById("wrapDiv"); var innerP = document.getElementById("innerP"); var textSpan = document.getElementById("textSpan"); // 测试直接绑定的事件到底发生在哪个阶段 wrapDiv.onclick = function(){ console.log("wrapDiv onclick 测试直接绑定的事件到底发生在哪个阶段") }; // 捕获阶段绑定事件 window.addEventListener("click", function(e){ console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.addEventListener("click", function(e){ console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.documentElement.addEventListener("click", function(e){ console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); document.body.addEventListener("click", function(e){ console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); wrapDiv.addEventListener("click", function(e){ console.log("wrapDiv 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); innerP.addEventListener("click", function(e){ console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); textSpan.addEventListener("click", function(e){ console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName); }, true); // 冒泡阶段绑定的事件 window.addEventListener("click", function(e){ console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.addEventListener("click", function(e){ console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.documentElement.addEventListener("click", function(e){ console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); document.body.addEventListener("click", function(e){ console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); wrapDiv.addEventListener("click", function(e){ console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); innerP.addEventListener("click", function(e){ console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); textSpan.addEventListener("click", function(e){ console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName); }, false); </script>
点击textSpan元素,如下图控制台输出:
1、所有在目标元素上绑定的事件,都会发生在目标阶段
2、在绑定捕获代码之前写了绑定的冒泡阶段的代码,所以在目标元素上就不会遵守先捕获后冒泡这一规则,而是先绑定的事件先发生。
3、由于wrapDiv不是目标元素,所以它上面绑定的事件会遵守先捕获后冒泡的规则。所以用onclick直接绑定的事件发生在了冒泡阶段。
事件委托
对“事件处理程序过多”问题的解决方案就是事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
例如,click事件会一直冒泡到document层次。我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
//点击li元素,输出li当中的颜色 <ul id="box"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul> //一般会这样写 <script> var colorBox = document.getElementById('box') var colors = colorBox.getElementsByTagName('li') for(var i=0;i<colors.length;i++){ colors[i].addEventListener('click',function(e){ var li = e.target; console.log(li.innerHTML) },false) } </script> //使用事件委托去写 <script> var colorBox = document.getElementById('box') colorBox.addEventListener('click',function(e){ var li = e.target; if(li.nodeName.toLowerCase() === 'li'){ console.log(li.innerHTML) } },false) </script>
事件委托还有一个好处就是添加进来的元素也能绑定事件
//点击li元素,输出li当中的颜色 <ul id="box"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul> <button onclick="add()">add</button> <script> var colorBox = document.getElementById('box') colorBox.addEventListener('click',function(e){ var li = e.target || e.srcElement; //兼容处理 if(li.nodeName.toLowerCase() === 'li'){ console.log(li.innerHTML) } },false) function add(){ var liNode = document.createElement('li') var textNode = document.createTextNode('this is add text.') liNode.appendChild(textNode); document.getElementById('box').appendChild(liNode) } </script>