一个小需求事情的起因,是昨天老板提出的一个需求。他要实现让我们自己定制的弹出层,具备按下 ESC 也能退出的功能。我把任务交给了同组的小伙伴S去实现。(这个项目用到了vue技术栈,以及饿了么的UI框架。)我开完会回来,发现他还在处理那个功能,但好像遇到了什么瓶颈。于是,我就问他,卡在了什么地方。小伙伴S说,他百度了不少资料,还查了官方文档,并且尝试其中的办法,但就是无法触发按下 ESC 的回调方法,很是郁闷。我看了他的代码,他的写法是这样的:<div class=“custom-modal” @keydown.27=“handlePressEscape”> …</div>…handlePressEscape () { console.log(‘press escape!’);},…他的想法不错,因为是自定义的弹出层,所以就想着把 keydown 事件,绑定在最外层的 div 上,让整个弹出层都能监听到。他给我看了他查的资料,几乎都是在 input 上绑定 keydown 事件的例子,而 vue 的官方文档里也是类似的例子,实践后却陷入了瓶颈。但是他忽略了一个问题,keydown 事件,并非绑在任意一个标签上,都会起作用。一种思路我没有直接把答案告诉他,而是给他提供了一个思路:在我们常用的 element-ui 的 el-dialog 组件里,有个属性叫做 close-on-press-escape,它的解释如下图:从文档的解释,可以看出 el-dialog 在默认情况下,已经实现了我们需要解决的需求。所以,我让他看看 el-dialog 的源码,是如何实现的。他一听要看源码,就露出了恐惧之情。源码是所有框架和API的根基,因为其高深度和复杂性,让人望而却步。我自己也经历过这个阶段,所以非常理解他的心情,鼓励他一起做一次尝试。查找源码首先,我们在 node_modules 里,找到了 element-ui的文件夹,它大致长这个样子:接着,我们找到了 packages 里的 dialog 文件夹,再从 index 入口,找到了组件 component.vue。可是,点进去找了半天,也只找到个 closeOnPressEscape 属性的定义,却没有实现的方法。…closeOnPressEscape: { type: Boolean, default: true},…这么神奇么?只定义一个属性,就能实现一个事件的交互了?感觉不太可能啊?!? 为了揭开迷雾,继续找。。。仔细浏览了 component.vue 文件,发现在 script 里,引入下面 3 个文件:import Popup from ’element-ui/src/utils/popup’;import Migrating from ’element-ui/src/mixins/migrating’;import emitter from ’element-ui/src/mixins/emitter’;…在第一个引入的 Popup 中,竟然也发现了 closeOnPressEscape,感觉似乎找对方向了。但令人沮丧的是,Popup 中同样只有 closeOnPressEscape 的属性定义,却没有实现。不过,它却引入了另一个辅助文件 PopupManager,再点进去找。哇!终于找到了!它的实现,是这样的:// handle esc
key when the popup is shownwindow.addEventListener(‘keydown’, function(event) { if (event.keyCode === 27) { const topPopup = getTopPopup(); if (topPopup && topPopup.closeOnPressEscape) { topPopup.handleClose ? topPopup.handleClose() : (topPopup.handleAction ? topPopup.handleAction(‘cancel’) : topPopup.close()); } }});原来,是在 window 上添加了事件监听 keydown,当监测到是 ESC 的 keyCode 时,则执行相关操作。模仿源码ok,现在已经知晓了原理,那就按照我们的实际需求,模仿改造一下:…props: { … closeOnPressEscape: { type: Boolean, default: true }},…mounted () { window.addEventListener(‘keydown’, this.handlePressEscape);},destroyed () { window.removeEventListener(‘keydown’, this.handlePressEscape);},methods: { … handlePressEscape (event) { if (this.closeOnPressEscape && event.keyCode === 27) { this.handleClose(); } }}在上述实现中,有2个需要注意的点:代码方面,在 mounted 中,给 window 添加事件监听之后,要记得在 destroyed 时,去除监听。业务方面,这是一个我们定制的通用的弹出层组件,所以在 props 中定义了一个 closeOnPressEscape 属性,以方便在某些业务场景下,不需要按 ESC 就退出这个功能的时候,直接设置它为 false 即可。源码真有那么可怕吗?源码一词,乍一听就是神秘、高大上、吊炸天的代名词,让很多的前端同学闻风丧胆。回想当初,我也曾一度对它有一股深深的恐惧。源码真的这么可怕吗?从以上的事例中可以看出,其实并没有。例子中的element-ui源码并不复杂,我和小伙伴S一起看源码时,他也大概都能看得明白。最后因为弄懂了背后的原理,进行简单应用,比较轻松地就解决了问题。对于源码的恐惧,让我们渐渐思维固化,自己告诉自己不要去碰源码,时间长了就遗忘了还有这样一条路可走。面试中的应用关于对源代码的考察,我也会经常应用在面试中。在面试中,如果候选人给我的感觉不错,我的惯用伎俩是问下面这个问题:刚才你说到,用过一段时间 xxx 框架,xx API属性也用过,也很清楚它达到的效果。那么现在,如果需要你实现一个类似的效果,抛开 xxx 框架以及 xx API属性,你会如何去实现,有没有其他思路?这个问题,意在考量候选人的思维方式和解决问题的能力,以及把他思考的过程阐述清楚的表达能力。这三种能力,往往比使用过某些框架的经验,更让我看中。这道题的回答思路,其实就是可以通过挖掘源码,去实现功能。另外也可以通过海量地查找资料,发现原生js的实现方式,但这条路没有直接挖掘源码来得快。在遇到实际的业务问题的时候,参考源码的原理和写法,往往能更快地解决问题。这是我自己对这道题目,给出的答案。一点点思考昨天的案例,引发了我的一连串思考:现代框架的确降低了前端入门的门槛,提高了开发效率。但是,在使用这些框架的过程中,我们到底学到了什么?脱离了框架和它的API,我们脑海中还剩下些什么?以至于,当下一个更新更棒的框架出现的时候,我们是否能够用已经学到的知识,帮助自己更迅速地上手?知其然,并知其所以然,学习所有的知识都应当有这种探索精神。我们不仅仅是代码的搬运工。领悟这些深层次的原理,比起仅仅熟练地掌握一门框架,要实用得多。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。