JS 奧義解析(3):模態框的設計

李世雲發表於2016-07-28

奧義是平凡中所蘊含的不凡,能有效地用於指導實踐

下定義

首先我們需要給模態框下一個定義,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之後執行一些事務(比如統計時間),但在設定定時之後立刻彈窗:

事實上這段程式碼的所有console輸出都將被無限延期直到使用者處理完彈窗為止。當然這段程式碼表面看起來不夠簡潔,但它其實包含了非常多的細節,在這個匿名函式被濫用、塊級作用域缺失、語義化隨意的時代,有很多細節值得我們深究,這部分我們容後再討論。包括setTimeout的效能問題,以及JavaScript單執行緒所帶來的問題或者說可以歸一到JavaScript單執行緒的問題。

至此我們都可以看到,一個基本的訴求就是,在彈窗的同時JavaScript可能需要繼續執行它該執行的事務,由此可以引申的討論很多,相信很多人都遇到過彈窗的同時點不了回退按鈕,非得關掉彈窗才能回退,所以很多現代瀏覽器多了一個禁止再次彈框的核取方塊,在一些瀏覽器上,比如Safari(version 9.1.1),alert彈出的同時,console沒法使用。

現代模態框的設計

改進了這種種之後(出現問題並進行不斷的改進,其實也就是當今網際網路快速迭代的核心驅動力之一),我們希望的一個模態框是這樣的:

  1. 長得好看(看臉時代無需過多討論)
  2. 不阻塞執行緒,可以一邊彈窗,一邊繼續執行任務跑得飛起
  3. 點選回退按鈕無壓力後退,無需關閉已經出現的模態窗,特別在SPA應用中尤為重要
  4. 彈窗不能重疊(dom只有一套,實際上不會重疊,這裡指多個訊息重疊時處理的靈活性)
  5. 需要在不關閉彈窗的基礎上更新呈現的訊息以及處理措施
  6. 應該有統一的回收機制

繞過了執行緒阻塞,可以讓我們有更大的發揮空間,但請記住Linux申請許可權時的經典名言:能力越大責任越大。擁有了一邊彈窗,一邊執行任務的能力之後,意味著你得清楚的知道哪些程式碼應該在彈窗之前執行,哪些應該在彈窗後執行,哪些又該在彈窗的過程中繼續執行,並且規劃好他們的結構。

為了能夠使彈窗不重疊,這裡需要引入佇列,所有彈窗以及相應的操作按佇列方式執行,這也就解釋了統一的回收機制,這就讓你有能力任何時候清空佇列,回收所有彈窗,這也解釋了當有超過一個彈窗的時候,我們可以直接無閃爍更新內容和操作或者等待使用者操作之後順序彈窗(這裡的彈窗指alert和confirm)。讀到這裡,你可能恍然大悟,這不就是單例嗎,是的,但是同時你也得明白單例並沒有什麼特別之處,也不能取代你真正的設計和實現,因為你得記住,不管你怎麼設計和實現,最後你只申請一個例項,它就變成了單例(當然有一點你得清楚,不要重複新增dom節點)。

這些設計和實現細節無非是對我們定義的模態框的設計和實現,當然它同時也是任務的分解,我們經常在工作中或者會議上看到大領導提出目標,然後相應的負責人做任務分解,看起來和聽起來都索然無味,但這卻是做好事情至關重要的方法。雖然我們抽象設計的過程跟公司或者企業的目標比起來微不足道,但做事的原理和方法是想通的。因為一個同樣簡單的道理,你得對這件事非常瞭解才能正確的做任務分解,而清晰的理解通常是做好這件事的關鍵,無論是追求巨大的目標還是做簡單的抽象設計。

附言

程式是理性的,但寫程式的人是感性的,而奧義本來也是平凡中所蘊含的不凡,所以這一系列的文章將更加偏向理論、哲學,是用普遍聯絡的思想來組織的,不僅僅侷限於JavaScript,也不僅僅只適用於程式設計,所以我們假設看這一系列文章的人都擁有良好的JavaScript基礎,或者根本不關心語言本身。

相關文章