javascript事件代理
在程式設計中,如果我們不想或不能夠直接操縱目標物件,我們可以利用delegate建立一個代理物件來呼叫目標物件的方法,從而達到操縱目標物件的目的。毋庸置疑,代理物件要擁有目標物件的引用。我們來看一下javascript的一個最簡單實現:
var delegate = function (client,clientMethod ){ |
return function () { return clientMethod.apply(client,arguments);
} |
} |
var agentMethod = delegate(client, client.method ) |
agentMethod(); |
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <script type="text/javascript"> var ClassA = function(){ var _color = "red"; return { getColor : function(){ document.write("<p>ClassA的例項的私有屬性_color目前是<span style='color:"+_color+"' >"+_color+"</span></p>"); }, setColor:function(color){ _color = color; } }; }; var delegate = function (client,clientMethod ){ return function() { return clientMethod.apply(client,arguments); } } window.onload = function(){ var a = new ClassA(); a.getColor(); a.setColor("green"); a.getColor(); //alert(a._color); document.write("<p>執行代理!</p>") var d = delegate(a,a.setColor); d("blue"); document.write("<p>執行完畢!</p>") a.getColor(); }; </script> <title>delegate</title> </head> <body> </body> </html>
或者我們改變一下第二個引數,傳個字串進去:
var delegate = function (client,clientMethod
){ |
return function () { return client[clientMethod].apply(client,arguments);
} |
} |
/****************略*******************/ |
var d = delegate(a, "setColor" ); |
我們還可以搞一些很好玩的東西,下面的例子取自一個日本部落格。
<script type="text/javascript"> Function.prototype.delegate = function(delegateFor){ return delegateFor.apply(null, arguments); }; var Hoge = function(){ this.open = function (){ return 'this is Hoge#open'; }; this.close = function (){ return 'this is Hoge#close'; }; }; var Foo = function (){ this.name = 'foo'; this.open = function (){ return 'this is Foo#open'; }; this.close = function (){ return 'this is Foo#close'; }; }; var hoge = new Hoge; var foo = new Foo; alert(hoge.open()); // this is Hoge#open alert(hoge.open.delegate(foo.open)); // this is Foo#open alert(foo.open.delegate(hoge.open)); // this is Hoge#open </script>
由於delegate實現目標物件的隱藏,這對於我們保護一些核心物件是非常有用的。不過,說實在javascript的delegate基本算是call與apply的傀儡,因為js中只有這2個方法提供了改變當前函式內部this作用域的功能。不過,要實現對類的委託而不是例項的委託,這就複雜得多。Prototype.js,YUI與JQuery都有相應的實現。具體自己可去參考它們的原始碼。接著下來,我將講述delegate在事件上的應用,這可是個無以倫比的東東,就是改變偵聽器的位置(改變事件的繫結物件)。或者說,它得益於javascript獨特的事件傳播機制,因此實現起來非常容易,大家要好好執行它,我以前也在富文字編輯器中用過。
$.addEvent(colorPicker, 'click' , function (){ |
var e = arguments[0] || window.event, |
td = e.srcElement ? e.srcElement : e.target, |
nn = td.nodeName.toLowerCase(); |
if (nn == 'td' ){ |
var cmd = colorPicker.getAttribute( "title" ); |
var val = td.bgColor; |
_format(cmd,val); |
e.cancelBubble = true ; |
colorPicker.style.display = 'none' ; |
} |
}); |
上面就是我在富文字編輯器擷取前景色與背景色的程式碼,注意,我是把事件繫結在顏色面板上,而不是面板上的那一個個格子上。為了直觀起見,便於大家操作,我改用下面這個例子:
現在我們要點選列表中連結,取出裡面的內容,傳統的方法,我們需要遍歷新增所有a元素:
window.onload = function (){ |
var nav = document.getElementById( "nav" ); |
var links = nav.getElementsByTagName( "a" ); |
for ( var i=0,l = links.length; i<l; i++)
{ |
links[i].onclick = function () { |
alert( this .innerHTML); |
return false ; |
} |
} |
} |
<!doctype html>
<html dir="ltr" lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<style type="text/css">
body{background:#fff;}
a { color:#8080C0;text-decoration:none;border-bottom:2px solid #fff;}
a:hover {color:#336699;border-bottom-color:#B45B3E;}
</style>
<script type="text/javascript">
window.onload = function(){
var nav = document.getElementById("nav");
var links = nav.getElementsByTagName("a");
for (var i=0,l = links.length; i <l; i++) {
links[i].onclick = function () {
alert(this.innerHTML);
return false;
}
}
}
</script>
<title>delegate</title>
</head>
<body>
<ul id="nav">
<li><a href="http://www.cnblogs.com/">部落格園</a></li>
<li><a href="http://www.w3school.com.cn/">W3School</a></li>
<li><a href="http://study.163.com/">網易雲課堂</a></li>
<li><a href="http://www.willspace.cn/">WILLSpace</a></li>
<li><a href="http://blog.csdn.net/willspace/">CSDN_WILLSpace</a></li>
</ul>
</body>
</html>
新的方法,用nav代理了它下面所有元素,讓它負責大家的onclick事件,包括它自己的,也不管是否為a元素,所以有時我們需要做一些判斷,但少了遍歷DOM樹,效率明顯提高。
window.onload = function (){ |
var nav = document.getElementById( "nav" ); |
nav.onclick = function () { |
var e = arguments[0] || window.event, |
target = e.srcElement ? e.srcElement : e.target; |
alert(target.innerHTML); |
return false ; |
} |
} |
<!doctype html>
<html dir="ltr" lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<style type="text/css">
body{background:#fff;}
a { color:#8080C0;text-decoration:none;border-bottom:2px solid #fff;}
a:hover {color:#336699;border-bottom-color:#B45B3E;}
</style>
<script type="text/javascript">
window.onload = function(){
var nav = document.getElementById("nav");
nav.onclick = function () {
var e = arguments[0] || window.event,
target = e.srcElement ? e.srcElement : e.target;
alert(target.innerHTML);
return false;
}
}
</script>
<title>delegate</title>
</head>
<body>
<ul id="nav">
<li><a href="http://www.cnblogs.com/">部落格園</a></li>
<li><a href="http://www.w3school.com.cn/">W3School</a></li>
<li><a href="http://study.163.com/">網易雲課堂</a></li>
<li><a href="http://www.willspace.cn/">WILLSpace</a></li>
<li><a href="http://blog.csdn.net/willspace/">CSDN_WILLSpace</a></li>
</ul>
</body>
</html>
為什麼它會行得通呢?!因為DOM2.0的事件模型是這樣的,如果某個元素觸發一個事件,如onclick,頂層物件document就會發出一個事件流,隨著DOM樹往目標元素流去,這就是傳說中的捕獲階段,也就是原Netscape的事件執行模式,沿途的元素如果綁定了事件,它是不會執行的!第二階段,就是到達了目標元素,執行它上面的繫結事件,但如果onclick只是個空實現,當然是沒有效果啦!第三階級,就是起泡階級,原IE的事件執行模式,從目標元素往頂層元素折回,如果沿途有onclick事件,就隨個觸發!因此我們是點選了a元素,但它的onclick事件為空,當事件流上浮到ul元素時,發現ul元素綁定了onclick事件,就執行當中的函式。如果ul的祖先元素也綁定了onclick事件呢?!繼續執行!有多少執行多少!看下面的例子:
<!doctype html>
<html dir="ltr" lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<style type="text/css">
body{background:#fff;}
a { color:#8080C0;text-decoration:none;border-bottom:2px solid #fff;}
a:hover {color:#336699;border-bottom-color:#B45B3E;}
</style>
<script type="text/javascript">
var addEvent = (function () {
if (document.addEventListener) {
return function (el, type, fn) {
el.addEventListener(type, fn, false);
};
} else {
return function (el, type, fn) {
el.attachEvent('on' + type, function () {
return fn.call(el, window.event);
});
}
}
})();
window.onload = function(){
var nav = document.getElementById("nav");
nav.onclick = function () {
var e = arguments[0] || window.event,
target = e.srcElement ? e.srcElement : e.target;
alert(target.innerHTML);
return false;
}
var wrapper = document.getElementById("wrapper");
addEvent(wrapper,'click',function(){
alert("冒泡過程連我也驚動了!");
});
}
</script>
<title>delegate</title>
</head>
<body>
<div id="wrapper">
<ul id="nav">
<li><a href="http://www.cnblogs.com/">部落格園</a></li>
<li><a href="http://www.w3school.com.cn/">W3School</a></li>
<li><a href="http://study.163.com/">網易雲課堂</a></li>
<li><a href="http://www.willspace.cn/">WILLSpace</a></li>
<li><a href="http://blog.csdn.net/willspace/">CSDN_WILLSpace</a></li>
</ul>
</div>
</body>
</html>
正由於這個特性,我們就可以利用ul的onclick去代理它下面所有元素的onclick事件。原來,我們需要給這些a元素準備五個偵聽器(EventListener),現在我們只需要1個,節省了4個,如果這個列表有一百行呢?就節省了99個!在商務應用,我們經過會遇到許多報表(grid,實質是用table做的),我們需要為每行新增懸浮變色效果與點選編輯功能,這就用到onmouseover 、 onmouseout 與 onclick事件,如果這個報表有五千行,我們也只需要三個偵聽器,節省了14997個!如果用傳統方法做這個grid,IE6這樣垃圾的遊覽器不卡到你吐血。因此,善用event delegation會大大提高我們程式的效能。
附上一些有用的連結:
文章轉自: