用無後臺架構的Vue2網頁來一秒呈現出你的leetcode源碼吧!

做爲一個前端,最近我也踏上了刷題的不歸路。原本想着天天留一個小時來複習和反思本身天天刷的leetcode,可是因爲leetcode的服務器實在是渣,國內訪問出奇的慢,致使這個過程的體驗極其噁心。html

因而本身寫了個leetcode的爬蟲,把本身在leetcode上經過的代碼爬到本地,核心是ES6的generatorco,工具在此:leetcode-spider。結合本身的使用體驗,優化改造了幾回,如今這個工具已經挺好用了,已經發布到NPM。前端

代碼已經爬下來了,做爲一個前端,確定是想搞點事情的(誤),確定是想把他們呈現出來的,我本身有博客,那麼難道我要把代碼一行行復制到博客裏面嗎,這麼多篇leetcode解題報告發到個人博客裏面,那個人博客徹底就被leetcode解題報告給淹沒了。vue

因此我仔細思考了一下到底該怎麼呈現呢,若是low一點,那就用node寫個後臺,把文件內容讀出來Ajax返回給我前端,前端網頁呈現出來就能夠了,可是這些解題源碼全都是靜態文件呀,這個網頁也不用動態邏輯,那我徹底能夠基於backend-free(無後臺)架構來弄啊,若是你用hexo等靜態博客,那你就明白我想作什麼了,我先把leetcode解題代碼寫進json裏,而後用Vue2.x作一個單頁應用的網頁,JS直接向靜態服務器發Ajax請求,去請求這個json文件,並把裏面的代碼內容呈現出來,一個leetcode源碼呈現網站就這麼搞起來了,並且是純靜態的,發到github pages或者你本身的服務器上,就直接上線了!線上地址在此:leetcodenode


藉助一個社會化評論插件,多說、暢言blabla,評論功能也有了,若是我想寫個人解題過程、心得、思路、感慨怎麼辦?也能夠搞啊,我在代碼文件旁邊寫個markdown文件,而後網頁也是向靜態服務器發請求,去拿到這個文件,也就能夠呈如今網頁上。因而一個帶搜索功能、帶評論、帶本身的解題心得、帶源碼、帶leetcode題目的leetcode博客就這樣搭建起來了。並且若是隻是寫解題心得,那麼根本就不須要別的操做,實時寫實時呈現,若是是爬取了新的解題源碼,那也就執行一個命令,更新json(時長不超過一秒鐘)。整個過程遠比你搞個leetcode博客什麼的輕鬆多了。react

項目地址在此:leetcode-viwerjquery

爬蟲工具leetcode-spider和Vue單頁應用leetcode-viewer的詳細使用方法你們能夠點開連接各自進去查看。都已發至github。git

我的以爲這還算是一個挺好的工具的,能夠跟千千萬萬刷題的同窗們交流心得,並且更重要的是這徹底能夠做爲一我的展現的平臺,找工做的時候把連接放在簡歷裏面做爲一項我的算法能力的展現也仍是挺好的(嘿嘿,做爲一個即將找工做的同窗,我就是這麼想的)。github

具體實現過程

先說說leetcode-spider

既然是要爬蟲,那確定是有大量的異步請求,對於這種高I/O密集的場景,Node.js自然適合啊,可是既然是大量的異步操做,並且是一環扣一環的異步爬蟲(也就是說要根據上一步的結果來發起下一步的異步請求),那麼確定要註定寫一大堆的回調了,即便徹底基於Promise,也是仍然有大量的.then和.catch綁定回調,流程控制能力不好,大量的.then並不比回調地獄的花括號好看多少。正則表達式

因此必然是要上[generatorco]或者[asyncawait],可是由於想着把這個工具開源和發佈到NPM讓更多的人使用,上了async的話就意味着須要使用者的Node 7.x版本以上了,場景仍是太侷限了,並且命令行模式開--harmony-async-await在node低版本上報錯,因此決定用[generatorco],若是你用過koa,那麼確定會明白這兩兄弟在異步場景的如絲般順滑的體驗。算法

用了co,那就要保證你yield出來的東西是promise或者是thunk函數,並且co最新版已經明確表示請不要再yield thunk了,co指不定哪天就不支持thunk了,因此就須要徹底基於promise,那麼問題來了,是否是意味着我須要大量的promise封裝呢?固然是不須要的,藉助於dead-horse大牛寫的thenifythenify-all,能夠將基於回調的function轉換爲返回promise的function。因此我一行promise封裝的代碼都沒有寫全交由thenify兩兄弟完成。

多說一句,thenify是用於把一個函數promisify,那thenify-all呢。好比node自帶的文件模塊fs,fs.statefs.mkdir,fs.writeFile等等都是fs對象上的基於回調的方法,若是你用thenify的話須要把上述三個方法逐一promisify,而且用3個變量存起來,使用起來比較麻煩。那使用thenify-all的就只用一行代碼:

let thenFs = thenifyAll(fs,{},['stat','mkdir','writeFile']);

如今thenFs這個新對象就是將fs的三個方法promisify後的對象,thenFs.stat,thenFs.mkdir,thenFs.writeFile都是能返回promise的方法了。

爬取過程

說完了promise封裝,來講說具體的爬取過程,爬取的過程使用了request來發起請求,使用cheerio來對返回的HTML內容進行解析,解析了以後就能夠用jquery的方法查找指定的DOM,這倆是Node爬蟲的經常使用工具再也不贅述。一開始先須要用戶寫好一個JSON文件,裏面寫明用戶名、密碼和用來解leetcode的語言。而後就用帳戶密碼登陸leetcode,作好cookie管理,而後請求這個接口api/problems/algorithms/就能夠拿到這個帳戶AC了哪些題,接下來到這些題目的頁面上爬下代碼就能夠了。直接一個接一個爬速率確定太慢,並且用node的優點就是能夠並行爬取,而我不用進行並行線程的操做管理。co對於並行發起請求有着很是友好的支持,你能夠yield一個數組,並在數組裏面存放你想並行發起的promise,或者用Promise.all對數組處理爲一個Promise以後再yield,這些都是可行的處理方案。

如今的問題在於用戶第一次爬取的時候,他可能AC了200道題,若是我不加控制的直接爬取兩百道題,一方面帶寬問題存在,另一方面leetcode的服務器是真的真的很辣雞,這個問題你們在國內使用leetcode的同窗確定深有感觸,我常常都是同時打開好幾道題,同時寫好幾道題,由於打開網頁太卡,提交代碼太卡,代碼出結果太卡....因此直接發出大量請求的話,就致使常常丟包、response返回不完整、鏈接中斷等等問題,因此決定改換策略,對併發數進行限制,當到達任務數上限時,有任務完成時,後續的任務才能夠開始,這一塊的實現是用了TJ的co-parallel。其實co團隊開發了大量的co流程的附屬控制工具,如併發請求的容錯控制co-gather,只取併發請求中最快的co-any等等,能夠在co團隊的項目列表裏面查看。

剩下的工做就是對爬取結果的保存了,這裏沒有什麼特殊的地方,藉助於node自帶的模塊fs就能夠完成,同時,我也將爬取結果保存在了result.json文件裏,這樣下次再爬的時候經過對比result.json裏的信息和從Leetcode網站抓取的信息,我就能夠知道哪些代碼以前已經爬取過了,不用再爬(畢竟leetcode真的很卡,能節約點時間就節約點時間)。

leetcode-viewer的搭建過程

leetcode-viewer是用Vue2.x搭建的單頁應用。以前重度使用Vue1.x,並用Vue1.x本身搭了個博客,可是自從開始刷題之後,就沒有跟上Vue的最新發展了,時間基本都投入在刷題和研究生畢設的開題上了,因此也想借這個機會學學2.0版本。

看了下文檔,其實沒有特別大的改動,主要是新功能的加入,api雖然變更了,可是主體思想仍是沒變,所以上手起來仍是比較快。

首先想了想用戶應該怎麼樣去使用這個網頁去構建起他本身的leetcode博客,一方面是以爲刷leetcode的同窗很是的多,可是前端的同窗很少,不是前端的同窗的話怎麼用得舒服,因此想到了徹底不用後臺,後臺寫邏輯很麻煩,並且別的同窗要搭的話確定得看懂代碼本身改,因此決定把要呈現的信息寫入到json裏面的形式來做爲數據的來源,由於任何一個靜態資源服務器好比Nginx、Apache或者github pages,國內的git cafe等等,當你把靜態文件如json、txt等等放在上面的時候,你直接去請求他們(好比在瀏覽器裏面輸入地址),服務器都是會把他們返回給你的。因此就讓用戶先爬好代碼,代碼爬下來後執行一行npm run generate(耗時不會超過1秒)把爬下來的代碼寫進json裏,而後單頁應用跑起來的時候去請求json就能夠了。這樣,一個以往應用於hexo等靜態博客的backend-free架構的網頁就搞起來了。這個網頁扔到隨便一個靜態服務器上就能夠上線了。

Vue2.0的踩坑經歷

生命週期鉤子

Vue 2.0感受變化最大的就是生命週期的一堆鉤子函數變了,不過原先1.0的鉤子函數其實並很差用,主要是1.0時期,開啓keep-alive和結合vue-router以後,就比較複雜了,當一個組件被切換掉時,他的destroy鉤子不會執行了,由於組件沒有destroy掉,留在內存裏,要用vue-router的deactivate鉤子,切換回來的時候組件的ready鉤子也沒用,要改爲用vue-router的canreuse鉤子,可是!坑爹的地方來了,有的時候canreuse鉤不中,deactivate也鉤不中...這就蛋疼了把,因此keep-alive原本是一個挺好的功能的,可是遇到坑的時候真心很麻煩,debug的過程很是痛苦,。

如今生命週期的鉤子變了以後,vue-router的組件期間的鉤子沒了,data,deactivatedeactivatecanActivate等等通通給幹掉了,只留下了導航期間的鉤子,這讓以往一直是在data鉤子裏寫數據獲取邏輯的我一開始有點暈,可是寫了幾行代碼後來反而以爲異常清晰了,其實1.0版本的vue-router的許多鉤子替代了vue的鉤子,致使vue自己的鉤子有點形同虛設的感受了,可是vue-router的鉤子又不如vue的鉤子好用。如今一刀切了以後思路就簡潔了,擾亂也減小了。

可是vue-router的data鉤子被取代了,那麼按照官方文檔,數據獲取的邏輯就放在了watch函數裏,經過watch $route對象的變更來進行數據獲取,可是$在你切換到其餘頁面以前這個watch方法也在起着做用,因此好比你在watch裏寫了當前頁面的數據獲取邏輯,那麼當你去到其餘頁面時,這個數據獲取邏輯也依然會執行,這個小問題就不太對了,因此我把個人數據獲取邏輯寫在了一個if語句裏,if先對this.$route.path進行正則校驗,校驗path是不是當前頁面的路由,若是你是切換到其餘頁面去的,那我就什麼都不作。

可是這種方法就使得我在代碼裏耦合了路由的設置,若是你改路由配置,不只要改Vue-router的配置,還要記得來改此處的正則表達式,這實際上是可能出問題的。若是你們有好的方法,歡迎提出來。

如何確保dom已經在document裏了

由於我想爲這個leetcode源碼的呈現網頁引入評論的功能,這樣源碼的做者就能和讀者溝通解題方法和代碼。而第三方評論插件並很多,這個功能天然實現起來也沒有問題。而暢言須要備案,因此我仍是回到了多說

可是多說是一個多年前寫的插件了,時不時出bug什麼的就不說了,彷佛官方也已經不維護了(前段時間微信沒法登陸多說的問題1個多月才修復),最關鍵的是他仍是之前的那種基於dom操做的插件,你須要全局寫好一個對象叫作duoshuoQuery,裏面寫入參數,而後加載一個外部的多說的JS,JS加載好以後你要本身建立一個div,在div的屬性上也寫入一些參數,而後對這個div執行一個DUOSHUO.EmbedThread方法,執行好了以後把這個div append到一個已經存在於document裏的元素中。

期間踩的一些坑就不說了,就說最後這個append到一個已經存在於dom裏的元素中,以前在vue1.0時代就在vue上用過多說,因此如何在vue組件裏保證這個組件裏的一個元素已經在dom裏這實際上是一個已經踩過坑。同窗別急着告訴我用$nextTick,我在vue1.0時也是第一時間想到$nextTick,但是發現不行,$nextTick執行的時候並無保證組件的元素已經加載到dom裏了。後來仔細查詢了一番以後,看到了尤大大在vue-router的一個issue下面回覆:

nextTick is intended to be used right after you modified some reactive data.

nextTick是計劃在當你更改了某些響應式的數據時使用的。

也就是說,nextTick應該被用在某些計算屬性或者watch再或者某個按鈕click事件綁定的methods當中。用來保證這些響應式的數據的變化已經反映到dom裏,但不是用來保證組件加載過程當中dom已經真正加載到document裏了。而尤大在那個issue裏提到的attached鉤子我試了,也並無保證元素已經加載進dom裏。後來採用比較hack的方法,就是不斷setTimeout檢測元素是否在dom裏來實現了功能。

上面這段vue1.0時代的坑,如今到了vue2.0確定要想着找一個方法解決,首選,如我所料,$nextTick在這個場景下依然不行(其實很好理解,$nextTick是基於MutationObserver,這個API你們可使用一下,是一個dom在它變化時會觸發事件告訴你他發生了變更,而如今咱們的使用場景下這個dom都還不存在,$nextTick固然不起做用)。不過,喜訊是mounted鉤子起做用了,可是問題又來了,mounted鉤子在開啓keep-alive以後只在元素第一次加載進文檔的時候執行一次,若是你切換到其餘頁面再切換回來,這個時候由於組件其實mounted過了,是不會執行的,因此你回來之後發現評論框加載不出來了,而給keep-alive組件使用的兩個鉤子activateddeactivated只能告訴你組件切換回來和切換出去了,並不能保證dom在文檔裏了。哎 心累啊...

後來因而放棄使用vue的方案來解決,之前在看jquery的$(document).ready()的源碼時,瞭解過,當時由於因爲低版本的IE瀏覽器裏,onreadystatechange事件不可靠,因此jquery爲了知道dom究竟是否進入異步事件狀態了,採用以下的代碼來實現ready():

try {
    top.doScroll("left");
} catch(e) {
    return setTimeout( doScrollCheck, 50 ); 
}

就是不斷的在網頁上觸發滾動事件,若是不能滾動,那說明還處在加載階段,就綁定50毫秒之後執行滾動事件,直到網頁能夠滾動了,就說明網頁加載好了,進入異步事件監聽狀態了。

因此個人實現方式是在組件的滾動事件上綁定了多說的啓動邏輯,這樣leetcode源碼和文章出來後,用戶滾動時才進行多說組件的加載工做,同時也能夠起到一個懶加載的效果。依然很hack,可是性能上比以前vue1.0時代的setTimeout好了點了。

值得總結的地方

其一就是錯誤處理,之前用koa的時候大多的是yield 單個異步任務,而如今我在爬取出了你AC出了哪些題以後,就要並行發起不少個異步請求了,也就是yield 一個數組,數組裏面存放了不少的Promise,那我是在每一個Promise後面寫好catch,仍是對這個數組執行Promise.all以後再在返回的promise上寫catch,仍是直接用try catch把整個yield包起來呢,這裏就須要對co的整個處理流程爛熟於胸,同時,還須要考慮你的容錯策略,並且並行發起請求了以後你是無法把嫁出去的女兒收回來的,若是一個出錯了你改怎麼辦,你知道已經錯了一個了在不能收回發出去的請求的狀況下該怎麼辦,錯了不少你又該怎麼辦?因此結合了本身的一些思考和研究。目前已經開了一篇文章講述co/koa中的錯誤處理,正在填坑,寫好了就發出來。

其二就是併發控制,前面說過用了co-parallel,其實他的代碼量很短,最近也會仔細分析一下實現的方法。

todo list

  • 網頁的響應式改造

    • 已完成

  • 使用Vuex進行改造

    • 當時想着這個應用的狀態很少,狀態的兄弟節點傳遞也挺少,就沒上Vuex,如今來看代碼在狀態管理這塊仍是太hack


以上大致就是我在寫這兩個小工具時的思考和過程了。

原發表於個人博客:歡迎圍觀

相關文章
相關標籤/搜索