zoukankan      html  css  js  c++  java
  • 为什么我推荐事件委托而不是批量绑定

    太长时间没写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之类。大部分情况还是可以考虑一下的。

    说不定可以解决一些意想不到的问题。(^^)

    简单的东西不用多说,一个小技巧,仅供参考而已,估计已经有很多同学早就开始这样做了。  

      

      

  • 相关阅读:
    OpenGL ES multithreading and EAGLSharegroup
    NSRange用法(转)
    多测师肖老师_mysql之视图(10.1)
    多测师肖老师__三表(9.1)
    多测师肖老师__多表练习(8.1)
    多测师肖老师__多表讲解(7.9)
    多测师肖老师_mysql之单表练习(7.5)
    多测师肖老师__单表建表和练习(1.8)
    多测师肖老师_mysql之单表和多表题(9.2)
    多测师肖老师___分享杭州面试题
  • 原文地址:https://www.cnblogs.com/hongru/p/2510960.html
Copyright © 2011-2022 走看看