上次說到我們在無菌環境中設計了一個事件代理函數,但效能是個問題,解決它我們需要緩衝節點集合,發現節點集合不足以應對新情況時,再替換這個節點集合,重新開始匹配。下面是新的方案:
var delegate = function(selector,type,callback){ var els = $(selector); addEvent(document,type,function(e){ var flag = true; var node = e.srcElement || e.target; for(var i=0,el;el = els[i++];){ if(node === el){ flag = false; return callback.call(node,e); } } if(flag){ els = $(selector); for(var i=0,el;el = els[i++];){ if(node === el){ return callback.call(node,e); } } } },true); }
<br /><!doctype html><br /><html lang="zh-ch" id="html"><br /> <head><br /> <meta charset="utf-8" /><br /> <meta content="IE=8" http-equiv="X-UA-Compatible"/><br /> <title>事件代理 by 司徒正美</title></p><p> <script type="text/javascript"><br /> window.onload = function(){<br /> var addEvent = function(el,type,callback,data){<br /> if ( el.addEventListener ) {//如自訂對象就綁定回呼函數了<br /> el.addEventListener( type, callback, !!data );<br /> } else if ( el.attachEvent ) {<br /> el.attachEvent( "on" + type, function(){<br /> return callback.call(el,window.event)<br /> });<br /> }<br /> }<br /> var delegate = function(selector,type,callback){<br /> var els = $(selector);<br /> addEvent(document,type,function(e){<br /> var flag = true;<br /> var node = e.srcElement || e.target;<br /> for(var i=0,el;el = els[i++];){<br /> if(node === el){<br /> flag = false;<br /> return callback.call(node,e);<br /> }<br /> }<br /> if(flag){<br /> els = $(selector);<br /> for(var i=0,el;el = els[i++];){<br /> if(node === el){<br /> return callback.call(node,e);<br /> }<br /> }<br /> }<br /> },true);<br /> }<br /> delegate("#list li","click",function(e){<br /> alert(this.innerHTML+e.type)<br /> });<br /> }<br /> var $ = function(selector,context){<br /> context = context || document<br /> try{<br /> var els = context.querySelectorAll(selector),<br /> result = [],ri=0,i=0,n=els.length;<br /> for(;i<n;i++){<br /> result[ri++] = els[i]<br /> }<br /> return result;<br /> }catch(e){<br /> alert("你的瀏覽器不支援querySelectorAll")<br /> }<br /> }<br /> var aaa = function(){<br /> var li = document.createElement("li");<br /> li.innerHTML = "動態添加"+aaa.aa++<br /> $("#list")[0].appendChild(li)<br /> }<br /> aaa.aa = 0;<br /> </script></p><p> </head><br /> <body id="body"><br /> <h1>司徒正美:事件代理</h1><br /> <ul id="list"><br /> <li>無序列表1</li><br /> <li>無序列表2</li><br /> <li>無序列表3</li><br /> <li>無序列表4</li><br /> </ul><br /> <p>下面的無序列表是沒有id的!</p><br /> <ul><br /> <li>無序列表1</li><br /> <li>無序列表2</li><br /> <li>無序列表3</li><br /> <li>無序列表4</li><br /> </ul><br /> <button type="button" onclick="aaa()">動態添加</button></p><p> </body><br /></html><br />
運行代碼
好了,我們現在來考慮另一種情況。之前我們的判定都是用全等於來比較,但如果事件來源是來自更底層的元素呢?換言之,是下面的情況。
<div onclick="alert('outer')" id="outer"> <div onclick="alert('middle')" id="middle"> <div onclick="alert('inner')" id="inner"></div> </div> </div>
當我們點擊inner元素時,它上面的middle與outer的onclick也觸發,因此我們必須引入包含判定了。這裡我直接給出答案,具體可見我的這一篇博文。
var contains = function(el, root) { if (el.compareDocumentPosition) return (el.compareDocumentPosition(root) & 8) === 8; if (root.contains && el.nodeType === 1){ return root.contains(el) && root !== el; } while ((el = el.parentNode)) if (el === root) return true; return false; } var delegate = function(selector,type,callback){ var els = $(selector); addEvent(document,type,function(e){ var flag = true; var src = e.srcElement || e.target; for(var i=0,el;el = els[i++];){ if(el === src || contains(src,el) ){ flag = false; return callback.call(el,e); } } if(flag){ els = $(selector); for(var i=0,el;el = els[i++];){ if(el === src || contains(src,el) ){ return callback.call(el,e); } } } },true); }
<br /><!doctype html><br /><html lang="zh-ch" id="html"><br /> <head><br /> <meta charset="utf-8" /><br /> <meta content="IE=8" http-equiv="X-UA-Compatible"/><br /> <title>事件代理 by 司徒正美</title></p><p> <script type="text/javascript"><br /> window.onload = function(){<br /> var addEvent = function(el,type,callback,data){<br /> if ( el.addEventListener ) {//如自訂對象就綁定回呼函數了<br /> el.addEventListener( type, callback, !!data );<br /> } else if ( el.attachEvent ) {<br /> el.attachEvent( "on" + type, function(){<br /> return callback.call(el,window.event)<br /> });<br /> }<br /> }<br /> var delegate = function(selector,type,callback){<br /> var els = $(selector);<br /> addEvent(document,type,function(e){<br /> var flag = true;<br /> var src = e.srcElement || e.target;<br /> for(var i=0,el;el = els[i++];){<br /> if(el === src || contains(src,el) ){<br /> flag = false;<br /> return callback.call(el,e);<br /> }<br /> }<br /> if(flag){<br /> els = $(selector);<br /> for(var i=0,el;el = els[i++];){<br /> if(el === src || contains(src,el) ){<br /> return callback.call(el,e);<br /> }<br /> }<br /> }<br /> },true);<br /> }<br /> delegate("#list li","click",function(e){<br /> alert(this.innerHTML+e.type)<br /> });<br /> }<br /> var $ = function(selector,context){<br /> context = context || document<br /> try{<br /> var els = context.querySelectorAll(selector),<br /> result = [],ri=0,i=0,n=els.length;<br /> for(;i<n;i++){<br /> result[ri++] = els[i]<br /> }<br /> return result;<br /> }catch(e){<br /> alert("你的瀏覽器不支援querySelectorAll")<br /> }<br /> }<br /> var contains = function(el, root) {<br /> if (el.compareDocumentPosition)<br /> return (el.compareDocumentPosition(root) & 8) === 8;<br /> if (root.contains && el.nodeType === 1){<br /> return root.contains(el) && root !== el;<br /> }<br /> while ((el = el.parentNode))<br /> if (el === root) return true;<br /> return false;<br /> }<br /> var aaa = function(){<br /> var li = document.createElement("li");<br /> li.innerHTML = "動態添加"+aaa.aa++<br /> $("#list")[0].appendChild(li)<br /> }<br /> aaa.aa = 0;<br /> </script><br /> <style type="text/css"><br /> #inner{<br /> padding:10px;<br /> width:50px;<br /> height:50px;<br /> background:#4DC2F2;<br /> }<br /> #middle{<br /> padding:10px;<br /> width:100px;<br /> height:100px;<br /> background:#FF98ED;<br /> }<br /> #outer{<br /> padding:10px;<br /> width:150px;<br /> height:150px;<br /> background:#00FF00<br /> }<br /> </style><br /> </head><br /> <body id="body"><br /> <h1>司徒正美:事件代理</h1><br /> <ul id="list"><br /> <li>無序列表1</li><br /> <li>無序列表2</li><br /> <li>無序列表3</li><br /> <li>無序列表4</li><br /> </ul><br /> <p>下面的無序列表是沒有id的!</p><br /> <ul><br /> <li>無序列表1</li><br /> <li>無序列表2</li><br /> <li>無序列表3</li><br /> <li>無序列表4</li><br /> </ul><br /> <button type="button" onclick="aaa()">動態添加</button><br /> <div onclick="alert('outer')" id="outer"><br /> <div onclick="alert('middle')" id="middle"><br /> <div onclick="alert('inner')" id="inner"></div><br /> </div><br /> </div><br /> </body><br /></html><br />
運行代碼
我們再把篩選事件來源的邏輯獨立出來,就變成下面這個樣子。是時候考慮如第一部分設計的事件系統銜接起來了!
var handle = function(e,obj){ var flag = true, src = e.srcElement || e.target, els = obj.nodes; for(var i=0,el;el = els[i++];){ if(el === src || contains(src,el) ){ flag = false; return obj.callback.call(el,e); } } if(flag){ els = obj.nodes = $(obj.selector); for(var i=0,el;el = els[i++];){ if(el === src || contains(src,el) ){ return obj.callback.call(el,e); } } } } var delegate = function(selector,type,callback){ var handleObj = {}; handleObj.callback = callback; handleObj.selector = selector; handleObj.nodes = $(selector); addEvent(document,type,function(e){ handle(e,handleObj) },true); }