「讀懂原始碼系列1」還在恐懼讀原始碼?看完這篇就不怕了

Micherwa發表於2019-01-09

一個小需求

事情的起因,是昨天有一個新的需求被提出。

需求是要實現,讓我們自己定製的彈出層,具備按下 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,它的解釋如下圖:

「讀懂原始碼系列1」還在恐懼讀原始碼?看完這篇就不怕了

從文件的解釋,可以看出 el-dialog 在預設情況下,已經實現了我們需要解決的需求。所以,我讓他看看 el-dialog 的原始碼,是如何實現的。

他一聽要看原始碼,就露出了恐懼之情。

「讀懂原始碼系列1」還在恐懼讀原始碼?看完這篇就不怕了

原始碼是所有框架和API的根基,因為比較複雜深邃,所以讓人很抗拒。我自己也經歷過這個階段,所以非常理解他的心情,鼓勵他一起做一次嘗試。

查詢原始碼

首先,我們在 node_modules 裡,找到了 element-ui的資料夾,它大致長這個樣子:

「讀懂原始碼系列1」還在恐懼讀原始碼?看完這篇就不怕了

接著,我們找到了 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 shown
window.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:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。

「讀懂原始碼系列1」還在恐懼讀原始碼?看完這篇就不怕了

相關文章