为什么我推荐事件委托而不是批量绑定
太长时间没写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><br> 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之类。大部分情况还是可以考虑一下的。
说不定可以解决一些意想不到的问题。(^^)
简单的东西不用多说,一个小技巧,仅供参考而已,估计已经有很多同学早就开始这样做了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!