奥义是平凡中所蕴含的不凡,能有效地用于指导实践
下定义
首先我们需要给模态框下一个定义,Wikipedia(这就是大名鼎鼎的维基百科,后续非常多的地方都会引用它的描述)称其为modal window
或者modal dialog
,我认为modal dialog
更加语义清晰,更具有语义化,因为通常模态框是需要用户交互的,就像发起一个会话一样。Wikipedia这样描述:
The modal window is a child window that requires users to interact with it before it can return to operating the parent application, thus preventing the workflow on the application main window.
所以我们可以这样理解:模态框是需要用户优先处理的一次交互或者会话,优先处理在JavaScript中解释为阻断线程,等待用户做出响应(此处需要举一反一地知道还应该有非模态框)。下定义的要义是要使你描述的事物区别于其他,从而让我们能够明确这是模态框而不是一个橘子或者水杯。那么模态框的实现一定要尊重其定义,或者说按照其定义实现。
事实上所有JavaScript的功能都是这样被定义并且实现的,只是我们称其为标准,也许说ECMA-262你会更熟悉一些。如果你放开眼界,就会发现,人们对于下定义的运用远不止于此,全世界都在绞尽脑汁已图对其善加利用。
下定义的意义和运用
当我们还在纠结于下定义的枯燥无味的时候,还在嫌弃的时候,我们没有意识到下定义不是所有人都有资格做的,只有那些最初的开创者和发现者才真正的有资格对他们开创和发现的东西下定义,命名也是一样,还记得那句谁发现谁命名吗?所以你在中学时候学到的无比嫌弃的说明文的下定义方法,其实其本身是至高的荣誉,并且只属于不断开拓的人(这个位置还应该举一反三的知道说明文还有那些常用的描述方法,因为他们居然可以和下定义并列,一定有神奇之处)。
作为普通人,对下定义的善加利用表现在你真正的理解某一个事物,因为只有你真正理解他的时候你才能正确的定义它。换位思考,如果你想知道你是否真正的理解了一个事物,最简单的办法就是给他下个定义。 举个简单的例子,JavaScript的数组有一个reduce方法,我们可以理解为归纳的意思,因为它跟数学归纳法的前半部分很像,于是你可以知道他是不断的运用数组的每一项值推导出一个最终的结果,再于是你可以猜到它应该有些什么参数,再加上你的经验记忆,你就能明确的知道它有些什么参数,这就是联想记忆法。
真正的联想记忆法一定是推导而来的,运用的是一种归一的思想,因为这正是计算机最初被设计的思想,它是二进制的,不管现在它的能力有多么的强悍,最终都是可以归到0和1的。JavaScript也遵循这一规则,它归于一切皆是对象。换位思考,整个JavaScript都可以由一切皆是对象推导出来,这本来是第一课,但是咱们后面再来分析。
这时候我们可能需要回顾一下我们是如何通过对reduce下定义从而不断推导、理解、并且运用这个方法的,这里不再赘述。
同样,react-native 有一个新的方法或者概念叫做reducer,这个东西就不太好理解,而且是区别于数组reduce的归纳的意思的,到目前为止我无法对它下一个特别明确的定义,并且这个定义能反应出它的设计思想。大家可以运用下定义的方法对其进行更深入的学习,共勉。
参考原生模态框实现
言归正传,尽管ECMA的文档极尽艰难晦涩,但这却是解决问题的一般方法,一般方法的共同特点都是自由散漫,简单到你不相信他能发挥巨大的价值。将其用于抽象设计,这就是抽象设计的第一步—对你想实现的东西下一个定义。
所以我们可以这么理解,为了实现模态框的优先处理,JavaScript采用了阻塞线程的实现方式来100%防止代码被继续执行,直到用户做出响应。既然JavaScript自身已经提供了模态框,而我们尽然还要重新对其进行设计和封装,那就不得不思考一下为什么我们要这么做了。解决这种二选一的为什么的最为简单并且行之有效的方法叫做swot分析。
那么什么叫做swot分析,现学现用,或者学以致用,我们给他下一个定义,那就是权衡利弊,根据利大于弊或者弊大于利做出取舍。你的记忆中一定或多或少有这样一句讨厌的话:请分析一下你有什么优势?有什么缺点? 这时候你可能恍然大悟:靠,这不是三岁的时候跟小伙伴分饼干所用的方法吗? 是的,但这并不影响它成为威震天下的swot分析法,这也正暗合一般方法的共同特点。非常多的重大决定就是这样做出的。
那么JavaScript实现的alert和confirm究竟有什么问题呢?第一个问题就是它会阻塞线程,众所周知,JavaScript是单线程的,线程资源是非常宝贵的,如果你熟悉JavaScript,你会知道JavaScript最经典的特性之一就是异步,当然异步也是由这个单线程执行了,这就更显现出了这个线程的可贵之处,毫不吝啬的说,容不得半点浪费。当然,除此之外,丑是其次的,相比之下不值得提起(所有的相比内部实现都是由swot分析负责实现的)。可以参照一个简单的实例,定时2s之后执行一些事务(比如统计时间),但在设置定时之后立刻弹窗:
1 2 3 4 5 6 7 8 9 |
var startTime = Date.now(); function calculateElapsedTime() {/*{{{*/ var elapsedTime = (Date.now() - startTime)/1000; console.log('elapsedTime:', elapsedTime); }/*}}}*/ setTimeout(calculateElapsedTime, 0); alert('click ok to continue'); console.log('you will say this right after you click ok'); |
事实上这段代码的所有console输出都将被无限延期直到用户处理完弹窗为止。当然这段代码表面看起来不够简洁,但它其实包含了非常多的细节,在这个匿名函数被滥用、块级作用域缺失、语义化随意的时代,有很多细节值得我们深究,这部分我们容后再讨论。包括setTimeout的性能问题,以及JavaScript单线程所带来的问题或者说可以归一到JavaScript单线程的问题。
至此我们都可以看到,一个基本的诉求就是,在弹窗的同时JavaScript可能需要继续执行它该执行的事务,由此可以引申的讨论很多,相信很多人都遇到过弹窗的同时点不了回退按钮,非得关掉弹窗才能回退,所以很多现代浏览器多了一个禁止再次弹框的复选框,在一些浏览器上,比如Safari(version 9.1.1),alert弹出的同时,console没法使用。
现代模态框的设计
改进了这种种之后(出现问题并进行不断的改进,其实也就是当今互联网快速迭代的核心驱动力之一),我们希望的一个模态框是这样的:
- 长得好看(看脸时代无需过多讨论)
- 不阻塞线程,可以一边弹窗,一边继续执行任务跑得飞起
- 点击回退按钮无压力后退,无需关闭已经出现的模态窗,特别在SPA应用中尤为重要
- 弹窗不能重叠(dom只有一套,实际上不会重叠,这里指多个消息重叠时处理的灵活性)
- 需要在不关闭弹窗的基础上更新呈现的消息以及处理措施
- 应该有统一的回收机制
绕过了线程阻塞,可以让我们有更大的发挥空间,但请记住Linux申请权限时的经典名言:能力越大责任越大。拥有了一边弹窗,一边执行任务的能力之后,意味着你得清楚的知道哪些代码应该在弹窗之前执行,哪些应该在弹窗后执行,哪些又该在弹窗的过程中继续执行,并且规划好他们的结构。
为了能够使弹窗不重叠,这里需要引入队列,所有弹窗以及相应的操作按队列方式执行,这也就解释了统一的回收机制,这就让你有能力任何时候清空队列,回收所有弹窗,这也解释了当有超过一个弹窗的时候,我们可以直接无闪烁更新内容和操作或者等待用户操作之后顺序弹窗(这里的弹窗指alert和confirm)。读到这里,你可能恍然大悟,这不就是单例吗,是的,但是同时你也得明白单例并没有什么特别之处,也不能取代你真正的设计和实现,因为你得记住,不管你怎么设计和实现,最后你只申请一个实例,它就变成了单例(当然有一点你得清楚,不要重复添加dom节点)。
这些设计和实现细节无非是对我们定义的模态框的设计和实现,当然它同时也是任务的分解,我们经常在工作中或者会议上看到大领导提出目标,然后相应的负责人做任务分解,看起来和听起来都索然无味,但这却是做好事情至关重要的方法。虽然我们抽象设计的过程跟公司或者企业的目标比起来微不足道,但做事的原理和方法是想通的。因为一个同样简单的道理,你得对这件事非常了解才能正确的做任务分解,而清晰的理解通常是做好这件事的关键,无论是追求巨大的目标还是做简单的抽象设计。
附言
程序是理性的,但写程序的人是感性的,而奥义本来也是平凡中所蕴含的不凡,所以这一系列的文章将更加偏向理论、哲学,是用
普遍联系
的思想来组织的,不仅仅局限于JavaScript,也不仅仅只适用于编程,所以我们假设看这一系列文章的人都拥有良好的JavaScript基础,或者根本不关心语言本身。