用无后台架构的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


以上大致就是我在写这两个小工具时的思考和过程了。

原发表于个人博客:欢迎围观

相关文章
相关标签/搜索