為什么我推薦事件委托而不是批量綁定


太長時間沒寫blog了,最近迷迷糊糊,又到一個周末,為了給自己一個交代,還是盡力記錄點東西吧。免得哪天失憶想回去找資料都沒地方找了。

今天要記錄的東西很簡單,就是事件委托。我相信但凡一個做前端方向的,甚至不是前端方向的編碼者,對於dom元素的事件委托應該都了解了。所以今天不是說“事件委托”是什么?而是說為什么需要它。

【基於前端模版的開發】

我們先說這個,為什么要先說這個呢,因為事件委托在這種模式下顯得比較有價值。

前端模版-相信大家也都耳熟能詳,玩的很溜了。web的越來越多的工作開始移交到前端來做。其中就包含這一個東西。當然,我們今天也不討論前端模版的優勢。而是要看接下來使用過程中可能遇到的一些問題。

比如簡單的,如下:

<!DOCTYPE html>
<meta charset="utf-8" />
<body>
<script>
var $T = new function () {
	var $ = this;
	// escape
    this.escape = function(string) {
        return (''+string).replace(/&/g, '&')
                          .replace(/</g, '<')
                          .replace(/>/g, '>')
                          .replace(/"/g, '"')
                          .replace(/'/g, ''')
                          .replace(/\//g,'/');
    };
    // template
    this.templateSettings = {
        evaluate    : /{{([\s\S]+?)}}/g,
        interpolate : /{{=([\s\S]+?)}}/g,
        escape      : /{{-([\s\S]+?)}}/g
    };
    var noMatch = /.^/;
    var unescape = function(code) {
        return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
    };
 
    this.template = function (str, data) {
        var c  = $.templateSettings;
        var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
            'with(obj||{}){__p.push(\'' +
            str.replace(/\\/g, '\\\\')
                .replace(/'/g, "\\'")
                .replace(c.escape || noMatch, function(match, code) {
                    return "',$.escape(" + unescape(code) + "),'";
                })
                .replace(c.interpolate || noMatch, function(match, code) {
                    return "'," + unescape(code) + ",'";
                })
                .replace(c.evaluate || noMatch, function(match, code) {
                    return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
                })
                .replace(/\r/g, '\\r')
                .replace(/\n/g, '\\n')
                .replace(/\t/g, '\\t')
                + "');}return __p.join('');";
                
        var func = new Function('obj', '$', tmpl);
        if (data) return func(data, $);
        
        return function (data) {
            return func.call(this, data, $);
        };
    }   
}

var tpl1 = '<div>我由第一個模版生成 {{= name }} <button onclick="say.call(this)">one</button></div>',
	tpl2 = '<div>我由第二個模版生成 {{= name }} <button onclick="say.call(this)">two</button></div>';
	
function say () {
	alert(this.value || this.innerHTML)
}
	
document.body.innerHTML = $T.template(tpl1, {name: 'Horizon'})
</script>
</body>

可能大家看這個:

var tpl1 = '<div>我由第一個模版生成 {{= name }} <button onclick="say.call(this)">one</button></div>',
	tpl2 = '<div>我由第二個模版生成 {{= name }} <button onclick="say.call(this)">two</button></div>';

看里面這個onclick總會覺得別扭。但是我們還得綁事件呀,直接用addEventListener或者attachEvent,一旦換模版重置innerHTML, 那之前的addEventListener不就沒用了么....

總不能每次都重綁一次吧?? 而且通常也容易帶出內存泄漏等等亂七八糟的東西。

所以,這時候,事件委托就有用了。

【利用委托進行事件派發】

一個簡單的委托例子可能像下面的代碼:

<ul id="dele-ul">
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ul>
<script>
var $E = {}; $E.on = function (o, e, f) { return o.addEventListener ? o.addEventListener(e, f, false) : o.attachEvent('on'+e, function () { f.call(o) }); }; $E.on(document.getElementById('dele-ul'), 'click', function (e) { var tar = e.target || e.srcElement; if (tar.nodeName.toLowerCase() == 'li') { alert(tar.innerHTML); } }) </script>

可能注意到里面有個過濾指定dom的代碼  

if (tar.nodeName.toLowerCase() == 'li') { ... }

當元素簡單時,當然還好,當dom層級復雜時。我們就需要做一點小手腳。

給我們需要綁事件的元素,加個標志位,然后利用事件冒泡即可。  

簡單的,比如下面的代碼:

$E = new function () {
    function on (o, e, f) {
		return o.addEventListener ? o.addEventListener(e, f, false) : o.attachEvent('on'+e, function () { f.call(o) });
    };
    function bubbleTo (el, endEl, key) {
        if (!el || (el && el == document)) {
            return null;
        } else if (el == endEl || (el.getAttribute && el.getAttribute(key))) {
            return el;
        } else if (el.parentNode) {
            return bubbleTo(el.parentNode, endEl, key);
        } else {
            return null;
        }
    }

    function dispatch (el, type, key, distributor) {
        if (typeof key == 'object') {
            distributor = key;
            key = 'data-cmd';
        }
        $E.on(el, type, function (e) {
            var tar = bubbleTo(e.target, el, key); 
            if (tar) {
                var cmd = tar.getAttribute(key);
                distributor[cmd] && distributor[cmd].call && distributor[cmd].call(tar, e, tar);
            }
        });
    }
	
	this.on = on;
	this.bubbleTo = bubbleTo;
	this.dispatch = dispatch;
}

原理就是加一個指定標志位,冒泡到有指定屬性的,就停下來,為他響應對應的事件。當然前提是有約定(這個約定屬性不能亂用 ^.^)

於是我們就可以這樣用:

<body>
<section>
	...
		<button data-custom-cmd="sayHi"><button>
        <div id="tpl-con">
            ...
        </div>
	...
</section>

<script>
    var tpl1 = '<a data-custom-cmd="walk"><span>{{= name }}</span></a>';
    // use tpl
    ...
    
    // dispatch Event
	$E.dispatch(document.body, 'click', 'data-custom-cmd', {
		'sayHi': function (e, tar) {
			// todo
		},
		'walk': function (e, tar) {
			// todo
		}
		...
	});
</script>
</body>

於是這樣,幾乎所有的 click 事件全部放到body上來處理了。 

不用擔心 templete 模板化,會把之前的事件弄沒了。

同時,也可以大大減少對於內存泄漏的一些擔心。

【后記】

當然,這只是一個很小很簡單的技巧和經驗,同時也不適用於所有情況,比如 resize, mousemove  之類的場景,基本不適用。 但是對於常用的類似click,mousedown,mouseup之類。大部分情況還是可以考慮一下的。

說不定可以解決一些意想不到的問題。(^^)

簡單的東西不用多說,一個小技巧,僅供參考而已,估計已經有很多同學早就開始這樣做了。  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM