-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前端进阶之路:点击事件绑定 #48
Comments
好文章,步进式的学习思路。 |
腻害,学习了,感谢博主 ❤️ |
非常好的文章。感谢楼主分享 |
思路很好啊,但是如果点击事件带参数的话就不能用了 |
把数据挂载到 Event 对象上叫事件参数,把数据挂载到 DOM 元素上叫属性。看你怎么组织/传递/获取数据了。 😃 |
在项目里用了action库,结果今天测试的时候发现在ios设备上的点击事件全部无效,搜了一下,好像是ios的Safari不支持click,不知还有没有其它兼容性问题 |
@toutouli 请参考这篇文档:《所有元素都可以用 Action 来绑定点击事件吗?》 另外请问你们的项目在哪里,可以访问到吗? |
@cssmagic 谢谢你的解答 |
好文,学习 |
我的博客也同步到issue中了,https://github.com/confidence68/blog/issues |
学习 |
厉害,也许各种库就是这样从小小的细节里面演化出来的~ |
整个思路很值得去学习,不断去改善代码中的痛点,才能从菜鸟一步步变大神:) |
学习了 思路好厉害 膜拜 题主团队还需要人吗? |
@liuyidi |
受益匪浅,谢谢分享。 |
群主思路好清晰,理得超顺,最佩服这种思路清晰的前端,赞赞赞 |
var htmlSurpriseTab = [
'<div>',
'<button id="lucky-draw">Lucky Draw</button>',
'</div>'
].join('') 这种方法拼接好赞~ 之前一直是用 |
@rccoder join 和 + 的性能在不同的浏览器上是不一样的 |
干货 |
魔法师好帅,我要给你生孩子。不过你别想了,我是个男的。 |
最近一直被事件绑定折磨,主要是不确定怎么做,为什么 |
学习了 |
@rccoder 带变量的字符串拼接怎么处理呢? |
@toutouli 在CSS中给你需要click的元素添加 |
还能这样写事件绑定,真的学习了!Mark |
感谢,学习了! |
大神啊,请教一下啊,事件绑定的过程中到底发生了什么,func会不断生成新的函数? function func(){
_.debound(()=>{
//
console.log('test')
},1000)
}
dom.addEventListener('click',func,flase) |
写的很棒啊,我也觉得我终于可以升级我的全局事件代理函数了,,我也觉得我的全局代理函数好臃肿,虽然只经过一次事件绑定在容器元素上面 |
@pengliheng var func = _.debound(() => {
console.log('test')
}, 1000)
dom.addEventListener('click', func, flase) |
引言
前端之所以被称为前端,是因为它是整个 Web 技术栈中距离用户最近、直接与用户进行交互的一环。而网页界面与用户的交互通常是通过各种事件来达成的;在各种事件之中,点击事件 往往又是最常见、最通用的一种界面事件。
本文将介绍我在 “点击事件绑定” 这一场景下的进阶之路。
背景
我是一个前端小兵,我在一家互联网公司做做一些简单的业务开发。
某一天,我接到了一个需求,做一个抽奖功能。公司里的前辈们已经完成了业务逻辑,而且已经提供了业务功能的接口,只需要我制作页面并完成事件绑定即可。
实践
开动
我写好了页面,页面中有一个 ID 为
lucky-draw
的按钮元素。接下来,我需要为它绑定点击事件。我是这样写的:这其中
BX.luckyDraw()
就是前辈们提供的业务接口,执行它就可以运行后续的抽奖功能。我测试了一下,代码工作正常,于是很开心地准备上线。
第一关
然而前辈们告诉我,这些重要功能的按钮是需要加统计的。这也难不倒我,因为我很熟悉统计系统的 API。于是我修改了一下事件绑定的代码:
这样做是有效的,但前辈们又告诉我,因为某些原因,统计代码和业务代码是分布在不同位置的,以上代码需要拆开。于是我尝试这样修改:
结果发现点击按钮时的抽奖功能失效了。原来,使用
.onclick
这样的事件属性来绑定事件有一个非常大的缺点,重复赋值会覆盖旧值。也就是说,这种方式只能绑定最后一次赋值的事件处理函数。我硬着头皮去请教前辈,才知道原来这种方式早已经不推荐使用了,应该使用 DOM 标准的事件绑定 API 来处理(在旧版 IE 下有一些兼容性问题,这里不展开)。因此我的代码改成了这样:
所有功能终于又正常了,我很开心地准备上线。
第二关
事实证明我还是太天真了,PM 是不会一次性把所有需求都告诉你的。原来,这个抽奖功能还需要做 A/B 测试,也就是说,只有一半的用户会看到这个抽奖功能。
这意味着用户的页面上可能根本没有
btn
这个元素,那么btn.addEventListener(...)
这一句直接就抛错了。因此,在为按钮绑定事件处理函数之前,我不得不先判断一下:虽然这样的代码在所有用户的页面上都可以正常工作,但这些预先判断看起来很蛋疼啊。我再次带着疑惑向前辈请教。前辈慈祥地看着我,说出了一句经典名言:
原来,神奇的 jQuery 允许我们忽略很多细节,比如这种没有取到元素的情况会被它默默地消化掉。而且 jQuery 的事件绑定方法也不存在兼容性问题,API 也比较好看。不错不错,不管网上的大神们怎么喷 jQuery,但它简直是我的救星啊!
于是,我的代码变成了以下这样:
我的代码看起来像那么回事了,我很开心地准备上线。
第三关
当然,我的故事不会这么快结束。要知道,对一个有追求的前端团队来说,不断提升用户体验是永恒的目标。比如,我们网站使用了一些方法来提升页面加载性能,部分页面内容并不是原本存在于页面中的,而是在用户需要时,由 JavaScript 动态生成的。
拿这个抽奖功能来说,抽奖按钮存在于一个名为 “惊喜” 的 tab 中,而这个 tab 在初始状态下是没有内容的,只有当用户切换到这个 tab 时,才会由 JS 填充其内容。示意代码是这样的:
这意味着,我写的事件绑定代码需要写在
// BTN READY
处。这种深层的耦合看起来很不理想,我需要想办法解决它。我想起来,我在阅读 jQuery 文档时看到有一种叫作 “事件委托” 的方法,可以在元素还未添加到页面之前就为它绑定事件。于是,我尝试这样来写:
果然,我成功了!好事多磨啊,这个需求终于开心地上线了。
经过进一步的研究,我了解到 “事件委托” 的本质是利用了事件冒泡的特性。把事件处理函数绑定到容器元素上,当容器内的元素触发事件时,就会冒泡到容器上。此时可以判断事件的源头是谁,再执行对应的事件处理函数。由于事件处理函数是绑定在容器元素上的,即使容器为空也没有关系;只要容器的内容添加进来,整个功能就是准备就绪的。
虽然事件委托的原理听起来稍有些复杂,但由于 jQuery 对事件委托提供了完善的支持,我的代码并没有因此变得很复杂。
多想一步
经过这一番磨炼,我收获了很多经验值;同时,我也学会了更进一步去发现问题和思考问题。比如,在我们的网页,通常会有多个按钮,那为它们绑定事件的脚本代码可能就是这样的:
我隐隐觉得这样不对劲啊!虽然这些代码可以正常工作,但每多一个按钮就要为
body
元素多绑定一个事件处理函数;而且根据直觉,这样一段段长得差不多的代码是需要优化的。因此,如果我可以把这些类似的代码整合起来,那不论是在资源消耗方面,还是在代码组织方面,都是有益的。于是,我尝试把所有这些事件委托的代码合并为一次绑定。首先,为了实现合并,我需要为这些按钮找到共同点。很自然地,我让它们具有相同的 class:
然后,我试图通过一次事件委托来处理所有这些按钮:
很显然,所有具有
action
类名的元素被点击后都会触发上面这个事件处理函数。那么,接下来,我们在这里区分一下事件源头,并执行对应的任务:这样一来,所有分散的事件委托代码就被合并为一处了。在这个统一的事件处理函数中,我们使用 ID 来区分各个按钮。
但 ID 有一些问题,由于同一页面上不能存在同名的元素,相信前端工程师们都对 ID 比较敏感,在日常开发中都尽量避免滥用。此外,如果多个按钮需要执行的任务相同,但它的 ID 又必须不同,则这些 ID 和它们对应的任务之间的对应关系就显得不够明确了。
于是,我改用 HTML5 的自定义属性来标记各个按钮:
我在这里使用了
data-action
这个属性来标记各个按钮元素被点击时所要执行的动作。回过头看,由于各个按钮都使用了这个属性,它们已经具备了新的共同点,而 class 这个共同点就不必要了,于是我们的 HTML 代码可以简化一些:同时 JS 代码也需要做相应调整:
我们的代码看起来已经挺不错了,但我已经停不下来了,还要继续改进。那个长长的
switch
语句看起来有点臃肿。通常优化switch
的方法就是使用对象的键名和键值来组织这种对应关系。于是我继续改:经过这样的调整,我发现代码的嵌套变浅了,而且按钮们的标记和它们要做的事情也被组织成了
actionList
这个对象,看起来更清爽了。在这样的组织方式下,如果页面需要新增一个按钮,也很容易做扩展:
到这里,这一整套实践终于像那么回事了!
开源
我自己用这一套方法参与了很多项目的开发,在处理事件绑定时,它节省了我很多的精力。我忽然意识到,它可能还适合更多的人、更多的项目。那不妨把它开源吧!
于是我发布了 Action 这个项目。这个小巧的类库帮助开发者轻松随意地绑定点击事件,它使用 “动作” 这个概念来标记按钮和它被点击后要做的事情;它提供的 API 可以方便地定义一些动作:
也可以手动触发已经定义的动作:
应用
Action 这个类库已经被移动 Web UI 框架 CMUI 采用,作为全局的基础服务。CMUI 内部的各个 UI 组件都是基于 Action 的事件绑定机制来实现的。我们这里以对话框组件为例,来看看 Action 在 CMUI 中的应用(示意代码):
只要当
CMUI.dialog.init()
方法执行后,对话框组件就准备就绪了。我们在业务中直接调用CMUI.dialog.open()
方法、传入构造对话框所需要的一些配置信息,这个对话框即可创建并打开。大家可以发现,在构造对话框的过程中,我们没有做任何事件绑定的工作,对话框的关闭按钮就自然具备了点击关闭功能!原因就在于关闭按钮(
<a href="#" data-action="close-dialog">×</a>
)自身已经通过data-action
属性声明了它被点击时所要执行的动作('close-dialog'
),而这个动作早已在组件初始化时(CMUI.dialog.init()
)定义好了。结语
希望本文对你有所启发,也希望 Action 能在实际开发中帮到你。
关于更多细节,欢迎继续阅读:
© Creative Commons BY-NC-ND 4.0 | 我要订阅 | 我要打赏
The text was updated successfully, but these errors were encountered: