姚雪

姚雪 查看完整档案

南京编辑南京审计学院  |   计算机科学与技术 编辑软通动力技术有限公司  |  前端工程师 编辑 www.sherryyao.com 编辑
编辑

写bug

个人动态

姚雪 赞了文章 · 2019-09-11

前端这些年我到底经历了什么(下)

上篇链接:前端这些年我到底经历了什么(上)

正文

2015 年 7 月,大学刚刚毕业的我进入了一家游戏公司做前端开发,在各种资源匮乏,前端只有自己一人的情况下如何进行突围?

在之前没有前端的情况下,后台工程师也能使用 JS 编写出大多数脚本,当时他们写的 jQuery 其实并不比那些专业前端差。那为什么还要招我进去呢?其实当时前端在后端眼里的主要职责还是主要为了画页面,做出拥有更好用户体验和美观的页面。因为绝大多数后台工程师的 CSS 能力真的很弱鸡,并且对写样式一直保持头疼。

起初在前后端没有分离的情况下,我需要将后台代码(python写的)在本地启动才能在浏览器中跑前端项目,甚至有些前端代码直接写在后台模版中,这种前后端耦合的情况让开发和维护效率都变得异常低下。

刚开始我也按照这种模式参与完成了两三个项目的开发,慢慢的后台开始将 JS 逐渐交给前端,当时使用的技术栈主要是:jQuery + Bootstrap。

庆幸的是,2015 年 AngularJS 在国内逐渐火了起来,为了跟上步伐,我阅读了很多相关的资料和书籍,记得当时比较火的书还是大漠穷秋翻译的《用 AngularJS 开发下一代 Web 应用》,是国内第一本关于 AngularJS 的书籍,这里的下一代 Web 应用主要指的就是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入,等等。

在当时的国内市面上很少有人会把这一概念应用到前端框架中,而 AngularJS 就是第一个吃螃蟹的。使用其提供的 ngRoute 模块前端可以控制路由的跳转,进行单页应用的开发。看到了这些亮点后,我便在组内项目中逐渐使用 AngularJS 来替换 jQuery,技术栈慢慢变为 AngularJS(1.x 版本)技术栈,前后端逐渐走向分离。

而没过多久(2015年下半年),Facebook 打造的 React 在国内异军突起,相比 AngularJS 的重而全,React 显得轻巧灵活一些(国内开发者似乎更青睐这样的,我也是),同时 Angular 对于开发一些中小型项目来说实在有点不合适,太难上手也成为其诟病之一。

于是乎很多前端开发(包括我)在开发项目时又多了一种技术栈选择:React 技术栈,结合当时蚂蚁金服出品的 React UI 框架 Ant Design,使用 React 来开发中后台系统变得更加便捷。

2015 年国内前端的巨变使我们组的几个后端工程师也开始慢慢重视前端,他们已经完全看不懂我写的前端代码,jQuery 操作 DOM 的时代也逐渐开始落幕,而此时 Vue 还不为人所知。

那时,我们工作主要的沟通方式还主要是 QQ 或 企业 QQ,作为一名程序员自然少不了加入各种 QQ 技术群潜水、冒泡或斗图。我算是比较关注群里各种大佬言论的人,有一次,群里有位大佬(其实是群主)说他目前的项目在使用 Vue,并且放言 Vue 在 2016 年会发展起来,建议我们尝试去使用它。

出于好奇,我便打开了 Vue 的官网,简单看了它的使用文档,瞬间便被吸引住了。不管是 API 设计,还是语法示例都十分优雅。我大致过了一遍文档就基本上手了,可见作者的用心之处。于是我很快也投入到了 Vue 的忠实粉队伍之中。

由于相对于 AngularJS 和 React 出生,Vue 显得非常渺小和普通,第一次在项目中使用的时候还遭到了后台同事的质疑,但是到目前看来,AngularJS 在国内的没落,Vue 受到追捧,当时的选择也便有了答案。

可以说 2016 年是 Vue 的元年,针对 Vue 的文章和社区也越来越多,慢慢的前端领域形成了“三国时代”,也可以看作是以 Vue 为主导的农民革命军拉开了一场史诗级的反美帝国主义战争。

在 2016 年组内的技术栈逐渐稳定,基本以 Vue 为主,React 为辅进行开发,而 Angular 因为在 16 年又发布了第 2 个版本,与第 1 版完全割裂,考虑成本等因素被抛弃在外。

2017 年因为公司发展的不景气及工资收入一直保持稳定,我选择了跳槽。此时我的简历(2年前端经验)中的技能是这样的:

跳槽面试可以说是非常成功,因为当时我所在地区前端招聘的时候 React、Vue 及 webpack 这样的技能在能力要求里还不是很常见(现在可以说是近 100% 出现了)。

入职后同样加入的是公司刚成立不久的部门,不同的是前端团队规模有 10 人左右,之后便开启了我的团队开发模式。

由于当时组内刚开始采用 Vue 做项目,我一进来上手可以说比较容易(对他们来说我可以算是 Vue 老手了),因此他们遇到问题便会来向我请教。同时在组内我也开启了前端相关的培训工作,如 Vue、webpack(在前端构建工具上,2017 年 webpack 已经占据了主流地位)、编码规范等。

2017 年后,前端框架开始趋于稳定,都各自发布了几个版本的迭代,前端技术慢慢的开始往跨平台、服务端发展,React Native、Node 在国内开始广泛使用,大前端的概念开始显现。同时 2017 年初,微信团队发布了小程序的正式版本。

2018 年,为了迎合部门发展需要,我开始着手部门 APP 中 React Native 的建设,与 iOS、Android 工程师配合实现 RN 的混合编程。同年,小程序发展迎来了春天,发展事态势不可挡,凭借微信流量平台,越来越多的企业开始将小程序开发纳入前端开发的技能领域。我也开始着手架构部门小程序的项目,将前端自动化、组件化理念渗透其中。

同时前端基础设施建设变得越来越重要,没有基建的前端团队很难得到发展,前端架构也越来越被人重视。随着 ES6、ES7 以及 TypeScript 的普及,JavaScript 编写变得越来越严谨和系统化。

而 Node 在前端领域的应用大大增加了前端系统的能力,如前端中间层的搭建。慢慢的一些中高级前端工程师开始往 Node 服务端探索。我们组内前端应用也相继使用 Koa2 框架搭建 node 中间层,用于处理接口转发及实现服务端的中转。

2019 年前端仍在不断变化着,前端微服务开始被越来越多人的讨论和实践,“任何可以使用 JavaScript 来编写的应用,最终会由 JavaScript 编写”的这个玩笑也逐渐变为现实。

最后更新下我的前端技能(4年经验):

前端路慢慢,吾将上下而求索。

关于

推荐关注我的微信公众号【前端呼啦圈】,定期分享原创和精选好文,第一时间获取推送文章。

查看原文

赞 20 收藏 9 评论 6

姚雪 赞了文章 · 2019-04-27

4 年前端狗,2 年 CTO

图片描述
图片来自 http://www.longroad.com.au/

我,Scott,一家创业公司的 CTO。

从业 6 年却很少写文章,近一年来接触了几十个刚毕业的前端新人,也面试了 100 多个前端工程师和 Nodejs 工程师,对于前端发展的这个职业算是有些感触吧,打算陆续写一些从业经验也好,技术分享也好,对自己前 6 年的经历做一些文字上的沉淀,按照我工科背景不善修辞的尿性,这些文章很可能凌乱也许更会烂尾,还请误入的童鞋谅解。

这是一篇准鸡汤文,谨献给工作 0 ~ 3 年的前端工程师,内容都是我的亲身经历,不精彩但接地气。

2010 年毕业于一所普通 211 大学,电气学院自动化专业,了解这个专业的大概知道自动化是弱电方向,就业高不成低不就,不读研仅靠本科的技术积累很难进入好单位,而我又是那种一进大学就迷失自我的 “逃课生”,四年逃课挂科无数,不仅荒废了专业,也虚度了青春,彼时年少而又轻狂,骄傲却不知路在何方,唯一的收获是大一便早早的搞定了老婆,恋爱 8 年后领了证,认识到现在已经整整 10 年,所以说码农恋爱要趁早啊。

以上是典型的屌丝工科男背景介绍。。。。。。

如下的一切,都源自毕业实习到了杭州钢铁集团,说是实习其实是见习,带着安全帽到各个生产线上看看钢材生产流程做个笔记,仅此而已。初到杭州,得知我有一个学霸级的高中女同学在杭州淘宝的广告部门实习,就在实习间隙登门拜访,学习求经。

入行以前

贵人点拨改换行业

见到高中女同学的同时,也结识了她学霸级的男朋友,跟她都同在淘宝,是一名资深的 Java 工程师,他俩知道我对互联网很感兴趣后,便向我口述了淘宝种种开放的职场氛围,有趣的江湖文化,每天发生的每一个故事都深深打动了我,激发起了我对于互联网的兴趣和想要进入淘宝的动力,后面两位开始给我出谋划策,鼓励我从事编程方向,比如 Java 编程,可以从自学开始。其实,早在 2009 年,就有朋友建议我从事前端开发方向,并且帮我选了月影的《Javascript 王者归来》这本大厚书,但是因为不够了解加上没有兴趣支撑,一直放角落里风干积灰,直到如今我也没看完。

初学 Java 编程

当时对于 HTML,CSS,Javascript,我其实一无所知,于是决定学习 Java,6 月份毕业后在杭州租了个小房间,每天啃杭州图书馆的 Java 书籍,同时在网上看马士兵老师的 Java 视频教程,陆续跟下来一个小聊天室和仿 QQ 通讯的小软件,但也只是照葫芦画瓢,并没有吃透技术点,心急如焚!加上这两个月窘迫到口袋里只剩下几十块钱,开始每天只吃一顿挂面,也是在这个时期留下了胃溃疡等一堆胃病,快撑不下去了,找工作就势在必行了。

clipboard.png

拖了很久去做的胃镜

寻找 Java 岗工作

从 8 月中旬开始逼自己投简历找 Java 工程师的工作,面过的 2 家公司都发了 offer,但薪资都是 1500,不仅是学徒工,更要签 4 年合同且不许毁约,想想有些后怕就跑去找之前提到的学霸女同学求建议,她男朋友建议我可以先考虑试试前端这个岗位,上手快并且我活泼的性格更适合设计相关的工作,回头想想,人生有时候不仅需要运气,更需要贵人点拨,于是当天下午回到住处便开始了为期 2 周的废寝忘食背书模式。

放弃 Java 投奔前端

所谓背书模式,其实就是把能搜到的前端知识都手抄到纸上,然后开始背诵,一天到晚脑海中不停的像放电影一样,熟记块状元素和行内元素的区别、绝对定位和相对定位的区别、jQuery 二级下拉菜单的 Javascript 插件代码实现。。。甚至背下了DOCTYPE、几种文档声明的写法,陈年小抄中,还能隐约看到 垂直居中 几个字:
图片描述
如果说前面的两个月是信心百倍,有的放矢的充电 Java,那么这两周就是完全是饥不择食,慌不择路的学习前端,在快要被房东赶出去的时候,再次逼自己开始又一轮的找工作。

面试上岗

面试前端岗位

精心准备了什么都精通的简历,也是第一份前端简历,投给了口碑网(淘宝当时内部新成立的项目)来练手,结果面试官电话里第一个问题就把我问懵逼了:

你知道 IE6 的双边距怎么解决么?

我嘞个大擦!这是什么鬼?!我听都没听过!!!

此时的我,根本不了解原来浏览器竟然有兼容性问题!也根本不知道前端开发是需要对 IE6 的样式和行为写 Hack 脚本进行兼容的(包括 IE7 和 IE8)!

再往后面又问到不同文档类型下的盒模型...,我就像是一个傻子一样,在电话这头大气都不敢出一个,真心想找地缝儿钻进去,气氛尴尬的要死。

面试官看出我啥也不会,便心平气和的给我普及了一下前端开发需要关注的知识点,不出意外的礼貌拒绝了我。

掉头重新学习前端

那次面试之后,我的信心受到了巨大打击,也发现了自己和一个哪怕是及格的前端开发工程师,相差的距离是如此的遥远,梦想看着就在眼前,而你臂展不够,各种不好的心理暗示开始浮上心头:

  • 淘宝肯定进不去了......
  • 杭州我白来一趟了......
  • 工作肯定找不到了......
  • 爸妈没法骗下去了......

此时已是 9 月出头,我亲爱的同学们都已经转正上岗,而我还在原地踟蹰。我只能安慰自己:你是 0 编程基础的新人(大学计算机都是挂的,对于挂计算机课还是略后悔的),不要拿自己和别人比较,这个阶段是正常的,挺过去就好了!

于是我努力回忆面试官的问题,重点突击 IE 兼容性问题,办法依然是背,感谢上苍赋予了我强大的临时抱佛脚能力,从小学到大学,都是以临考突击的方式应对,短期记忆量可以很大,但说忘就忘的一干二净,幸好有面试官的耐心提点,我才有的放矢,后来面试官我们私底下成为了很好的朋友。这一次背书只经历了 3 天 3 夜,对的,不眠不休的记忆,我便又开始新一轮的求职经历。

第三次求职面试

这一次花了一下午做简历,只投给了两家小公司,其中一家 HR 效率极高,几个小时候后便电话联系面试。去了后做了一套笔试卷子,我就和公司老总在办公室聊起来了,没想到越聊越嗨,为什么呢,因为我俩聊的是足球,我大学一直是院队主力左后卫,踢了四年校联赛,于是跟这个老总在足球这个话题上开始投机。

有时人生真是捉摸不定的曲折离奇啊,第二天中午我便拿到了这家公司的 offer,但 offer 的内容是:

把你外派到淘宝当外包,你愿意不愿意?工资会比较低

当下才知道这家公司是外包公司,思前想后,决定还是接受这个 offer,即便薪资低的可怜,勉强够交房租。

不过这只是第一关,还需要到淘宝去参加第二轮面试,被淘宝面试选中才能最终获得外派资格,于是屁颠屁颠跑到华星科技面试,开始依然是做了一套笔试题,面试官看完我答的卷子后是这样跟我讲的: 果然是刚毕业的学生,卷子填的满满的,但是题目全部做错了,又问了我 apply 和 call 的区别,我自然是不会的,甚至连上下文都搞不清楚是啥意思,只知道它俩传参的区别,于是面试官问我其他方面的,比如为啥从事这个职业之类的,我拼尽全力声情并茂讲述我的求职渴望,这也是我唯一可以讲的东西了。

走出华星科技大厦,心情是灰色的,感觉就要和淘宝真正的 say byebye 了,背的知识点遇到资深的面试官随便一挖,我便原形毕露伪装失败,越想越伤心,此时竟然雷声大作瓢泼大雨没头没脑哗啦啦的下起来了,我把档案塑料袋顶在头上,默默的往家走,躲雨的心情都没有,脑海中一片空白,雨越下越大,迷住了前面的道路,迷住了我的视线...

雨过天晴

人生是如此的曲折离奇,第二天便接到通知,淘宝竟然要我了!于是跑到外包公司签了就业合同便去淘宝报道了,此时是 2010 年 9 月 6 号,到了淘宝才意外发现,我竟然跟之前的学霸女同学在同一个大部门 - 淘宝广告事业部,人生处处是惊喜,一言不合就相遇。

自此我开始了 4 年的前端工程师之旅。

自知才疏学浅,我进入部门后,也是尽量的谦虚做人,不懂就问,跟同事好好相处,同事帮我解决了一个问题,我就基于这个问题,晚上到家后,根据他的解决思路,把所有相关的知识点,全部在百度搜一遍(那时候还不会科学上网),如此坚持了一年,这第一年技术也成长迅速,在外包公司的工程师队伍里已经是走在最前列了,那么外包公司也是给出较大幅度的涨薪,但是跟阿里正式的前端工程师待遇依然不可同日而语,毕竟是外包,要赚你的人头费,不过之后没多久淘宝觉得我已经完全达到正式工程师的水准了,便赔了外包公司一笔钱(这个我没考证,是听外包同事说的),于是我就欢天喜地的转正成为淘宝正式员工,但和外包公司老总依然成为了很好的朋友,同时也组建了这家公司成立以来的第一支球队,作为队长我带领外包公司的球队在杭州城参加各种比赛,虽然胜负参半也没拿到太好的成绩,但是跟这一波外包公司的队员一起风吹日晒, 建立了珍贵的革命友情,后来随着转正淘宝正式员工并且加入了淘宝的球队之后,跟老球队一起踢球的次数越来越少了...

职业沉淀

4 年阿里前端生涯

铺垫了那么多转行求职历程,其实只是为了说明,我是非常普通的一个人,跟所有的求职者经历不同但起点类似,有提心吊胆,有惊喜连连,有彷徨失落,有坚决果敢...,但选择了前端开发,无论它是不是不归路,都是我自认为的一条很适合自己的路,认识到这一点,就要铁了心走下去。

在阿里的四年,最大的收获是见识到了什么是大牛。在公众视野中不少活跃的各种圈内牛人红人(包括我自己,有些新人认为我很牛),接触下来其实根本比不上大公司里那些低调做事的大牛,他们做人做事,对自己的定位、要求和产出,总是能让我意识到自己的种种不足——不够谦虚不够努力。如果说这是榜样的力量,ok,那我承认与优秀的人共事总是十分难忘的经历,而我自己作为一个普通的工程师,自认够努力,但还远不够优秀,也许是天分不足,也许是尘世扰心,我的职业瓶颈还是早早出现,而要迈过去需要更长时间。

在工作 2 年左右,我发现自己在技术深度上很难有专业造诣的时候,我开始把眼光放的更长远,我不再把自己仅仅定位成一个前端工程师,而是会开始关心互联网的发展趋势与整体格局,关心产品从 0 到 1 诞生过程中的盈利模式,关心在产品迭代中工程师与运营多方参与的角度和结合的效率,关心工作流程和团队文化,这一切其实都是我潜意识不自觉的关注,是一种不安分,过后很久才逐渐意识到自己早就在默默铺路,有时候一个选择早就做了,只是你还未意识到。

那么对于技术这个领域,我也开始去关注更多非前端的生态,从 Linux 生产环境配置部署到域名解析,到软文策略和 SEO 推广,从移动端、 PC 端的前端分离和架构,去折腾 PHP 的 Wordpress,Codeigniter...,折腾 Ruby on Rails,直到接触到 Nodejs,尝试借助 Nodejs 拉上 Mongodb 做数据接口,和模板输出的工作,从数据库的安装到主从灾备,不求精通只求通,下班的无数的晚上和周末,在屏幕前消耗青春,通过乱七八糟的折腾这一系列有的没的,我逐渐意识到自己可以创造的价值不再是仅仅前端页面的输出,这个价值不单单是指可以一己之力搭建一个完整的网站,而是有了前后的动手经验,能从更高的一个层次上去把握一个项目以什么样的形式去设计,以什么样的思路去迭代会更加的省时省力,这个省时省力其实能更好的促成产品,拿到商业目标,而有了这个意识,就能更好的配合产品经理实现他脑海中的想法,如果你认为是不切实际海阔天空的想法,没关系,来小成本验证一下,验证之后说不定就真的是海阔天空,也是这种想法促使我业余时间结交了许许多多的朋友,以兼职的方式参与了至少 3 个小创业项目,自己的技术面和技术深度也得到了很大的拓展。

发掘自己的更大价值

当聚焦的点不再单单是前端开发的时候,我发现了更多的乐趣和更多的可能性,从前,我会花好几天反复测试一个广告投放模板,对于里面的一个模块加载器或者特效组件如何去设计才能最小化体积,减少展示延迟,最大化优化动画流畅度,针对移动端的广告投放用哪些效果兼容性最好最省电这些细碎的点占据着我工作的大部分时间,必须承认,解决这些技术点是很有成就感的,但是技术的革新速度实在太快了,昨天还在纠结的几 K 大小带来的网络延迟、代码执行效率带来的性能消耗,今天突然在百兆宽带甚至是 4G 大水管高规格硬件时代变得不那么敏感了,昨天还是小心翼翼使用 radius 圆角图片两套并存的兼容方案,今天 CSS3 属性放开随便用,这些当然是我们需要不断更新的知识栈,但仔细想来,让我花费数倍的时间去研究底层的优化细节,跟实现我的个人价值放到一起的时候,是不是最佳的途径呢?很多类似的技术问题,我可能需要花费 2 天,而天分好的同事,只需要花费 1 天甚至更少,从这一点上,我越来越不认可自己的技术方向和优势,于是我摘掉带有职业界限的眼镜去看待彼此的分工,带着合作共赢的心态去接触更多的领域,包括前面我提到的小创业团队,他们都没有能够生存下来,有的是因为技术原因,而有的是因为资源问题,参与这些项目也给了我更多的感触,让我逐渐明白想要做好一件事情,是要先定位实现的程度也就是预期目标,这个不同时期的目标完全取决于不同时期的资源配置,平衡追求这个性价比才是做事的关键,说白了,就是知道什么可为不可为,外加一些方法论。

创业萌芽

职业波动期

在阿里工作的第 3 年节点上,我萌生了创业的想法,但并没有机会也没有胆量迈出这一步,并且就在这个第三年中,我在阿里迎来了职业生涯中最心旷神怡的几个月,被分配到了技术非常优秀的主管,也开始参与更有挑战性的项目,个人技术成长越来越快,无论是前端开发能力,还是后端开发能力,然而好光景持续了仅仅几个月,便被分派去支持双 11、双 12 推广会场。在阿里这种变化是常态,业务支持永远是第一位的,因为客户永远是第一位的,所以抽调出来支持一个活动是很正常的,我起初也是很平静的参与这个项目,然而这个活动的历程却充满了心酸,耗尽了我的耐心。

成长受阻,陷于无序的需求算是一个促使我做出离职创业的导火索吧,另外一个重要原因则是遵从内心的创业冲动,后文会写到。

关于这个项目呢,原本是提前了 2 个月筹备这个活动,从后端数据到前端和运营,全部到位了,虽然前端在里面扮演的角色很关键,但技术难度却并不高,工作量也不大,就是把页面过来时候带的参数,按照规则梳理一下,发一些请求到后端,拿到加密值然后重新替换到页面里的所有 a 标签 href 中,也就是刷页面参数,然后把这套代码抽象成组件,集成到一个大系统里面,根据不同的会场类型加载不同的刷参逻辑。

原本是 3 周做完的项目,用了整整 7 周的时间才最终落定下来,主要原因是产品经理更换了 3 个,需求方大 Boss 介入了好几次,项目经理风险意识不够导致工作不断重做,解释成本也因此居高不下。每一次联调出问题,都先会把责任全部抛过来丢给前端,也就是我,然后每一次我都要反过来充当半个项目经理的角色,去 push 各个参与的团队来配合我核查问题的来源,而每一次查出来后要么是后端数据环境更改异常,要么是数据引擎切换引发不兼容,要么是产品设计规则不全出现参数漏洞,总之就是项目负责人的不专业,加上团队协作的成本高,导致参与的人都做的不开心,其实在这之前也参与过类似这样的扯皮的延期项目,然而这一次对于我却尤其难熬。

挣扎在白天黑夜边缘

在双 11 项目远没开始的时候,我接触到了一个创业项目,兼职赚外快补贴家用,平时的晚上和周末断断续续在做,大概到了 10 月份,这个项目准备去找融资,因此希望我在 10 ~ 12 月份能尽快把项目原型开发上线,我评估了一下剩余的工作量,差不多需要 15 人日,同时算了一下时间,10 月份至少有 3 周的空闲周末,还不算晚上,再加上 11 月份甚至 12 月份的晚上和周末大概能有 20 多天的时间,时间简直不能再充裕了,于是便也一口答应了下来。

然而这个从双 11 前就启动,一直持续到双 12 的公司项目,却频频上线出问题,而每一次出问题,都需要前端工程师介入联调,于是几乎所有的周末全部都被公司征用,不止如此,白天只能调试,不能发布,需要熬夜发布,出问题后还要配合回滚,回滚后,还是不能走,要等测试工程师测试完,再重复以上过程,于是就有了连续的周末通宵,这个答应在 12 月份上线的创业项目,便搁浅了,毕竟公司事大,孰轻孰重我还是分的清楚。

总算双 12 上线一周后,我下班回到久违的家中,眼看要延误掉这个创业项目的融资窗口,我想既然承诺了,无论如何都要拿下!于是,白天去公司工作,幸好是双 12 之后,这个项目不再变动了,每天只需要配合调整策略参数就行,不需要投入大精力去开发。晚上回家以后,7 点开始写代码赶这个创业项目,敲代码一口气通宵到到第二天早上 6 点,洗漱一下出门到公司吃个早餐,然后在公司躺椅上睡到 9 点,起来洗把脸,冲上浓咖啡,配合这个双 12 的 项目继续做沟通啊,邮件通报啊,参数更正啊这些琐碎的事情,中午吃过饭,又在躺椅上睡一个小时,下午跟进一些常规的部门工作,晚上回家后继续写代码,如此反复,经历了大概 12 天,期间至少有 2 次半夜敲着敲着代码就一头栽在桌子上睡着了zzz。要预发布创业项目的那个晚上,需要跑通发布环境,我买了一箱的红牛,一夜喝了 5 罐,凌晨 4 点,突然一阵恍惚,有一种灵魂出窍的感觉,手脚冰麻,说不出一句话,视力急速下降,那一刻,我想,我怕是要猝死了。

在这种症状持续的 2 分钟时间里,我心里怕到了极点,想了许多许多,如果我猝死了,父母怎么办?外婆怎么办?我的事业怎么办?这个项目怎么办?我闭上双眼,让自己使劲喘气,慢慢的回过神来,然后起来走动了一下,狠了狠心,继续折腾发布环境,差不多 7 点多,环境弄好后,就洗个脸到了公司。这一次到公司没敢睡觉,担心躺下了会彻底体力崩溃,就咬牙坚持了 1 天,晚上回家又熬夜大概到 2 点,成功发布上线,整个心才真的像大石头一样放了下来,顿时困倦无比,迷迷糊糊正打算去睡,突然又发现一个环境异常问题,赶紧屏住呼吸紧急调试,这一次调试的每一秒钟我都记忆深刻,因为我需要把已经休眠的身体和意识拉回来,大概 5 分钟左右重新修复上线,我给公司主管发了短信上午在家请假休息,就混沌沌的睡去了。

大概 2 周后,这个上线的创业项目拿到了 200 多万的天使投资,这个就是促使我做出出来创业的重要原因吧,这也是我目前正在创业的项目。

离职创业

在大公司当一颗螺丝钉,其实我并没什么不爽,但当发现一些不好的兆头或者流程大问题的时候,由于我人微言轻,再好的技术,或者说再好的工作态度都无法改善这个情况,我做了反馈但所有人都摇头,我做了推动但所有人都表示无能为力,这种无力感越来越强,我发现除了被我抓在手中的这些前端技能和可怜的工资,我什么也没有,什么也做不到,我开始怀疑人生,是坚持死磕到公司上市死磕到自己在公司和团队有更好的能力和话语权,还是选择离开。

这时候已经是 2014 年春天,我重新开始考虑去向,这时候年前参与的创业项目投资到位了,我也基本了解了它的业务模式,可能是年前拼命赶项目那次太投入,导致我对它也有很深的感情,毕竟是自己一手写出来的代码,是自己一手养大的孩子,这时候对方希望我以技术合伙人的身份加入,我最终想了想便答应了,其实还是有点冲动的,因为恰恰是那个时候是我在公司做项目最不爽的时候,在公司的无力感和在创业项目上的认同感成就感形成了鲜明的对比。

Anyway,我是希望可以通过我的力量,证明一些或者是实现一些我的想法,我希望可以有一些事情是可以在我影响和控制范围之内的,于是到了 6 月份,也就是阿里巴巴上市前夕,我递交了辞职书,这次走的虽有留恋但是毫无遗憾。

创业狗的日常

创业到今天,已经两年多,走的并不轻松,而创业两个字跟 CTO 一样,都是听起来真的很高大上,但是做的事情可能是灰突突的并无新意,唯一不同的时候,这是一条踩在我自己脚下的路,我走快一点就看到目标近一些,我走慢一些,就看到远方模糊一些。

创业有太多的不确定性,最大的不确定性就是能否兼顾好自己的身体和家庭,因为无论是时间投入还是资金回报,都是看公司状况和市场反应,所以像 i5ting 这样的 CTO 也要出书立作,也要有在线课堂,在拓宽人脉影响力和技术布道修炼的同时,也可以赚点小钱贴补家用,没错,就是贴补家用,不要以为创业公司合伙人都是高富帅,白富美,其实我认识的不少也仅仅是够花,甚至是穷屌丝,我也不例外,所以和慕课网合作讲授自己的前端实战经验,以下是我 2015 年底起业余时间录制的一些 Nodejs 进阶学习课程—— 《Nodejs 上线部署/React Native/Koa2/7天搞定 Nodejs 微信公众号开发等》,刚入行的新人可以参考我这个老人开发一个小项目的切入方向,开发功能所涉猎的角度,以及推进的方式是怎样的,如果你资金尚且充足,对知识又充满渴望,不妨看看这个视频课程,支持下我这个所谓的创业狗 - 草根 CTO,这是个硬广,对,说到硬广,我这一年来招进来的员工,还真的的确是看过我慕课网 Nodejs 课程入门这个行业的,所以我还是很欣慰的,其他的一些 Nodejs 课程是 2 年前创业前后边做项目边录制的,其实比较过时了,居然还可以帮助到入行的新人,我已经很满足了。

写在后面的话 - 所谓 CTO 的称谓

对外面我总是宣称自己是这家创业公司的 CTO,而对朋友和同事,我总是希望大家把我当成一个普通的程序员看待。因为对外,可以树立公司的形象和减小解释成本,这个 CTO 的 title 有明显的代入感,而对身边朋友,包括对我自己来讲其实它意义不大,我丝毫不认为有一顶 CTO 的帽子在头上,技术就能牛逼几分,身价就能增加几分,相反,有这顶帽子在头上,带给我更多的是一种责任,而恰恰,我慢慢成为了一个有责任感爆棚的人。在技术方面,我始终不认为自己可以配上 CTO 这个称谓,只不过对当下这个团队这样的业务规模,我尚可以完全 handle 住,但至于未来会怎样,依然还是个未知数。

后面我的文章,可以聊一聊我是怎么学习 Nodejs,我是怎么学习某些技术点,我是怎么对待技术和创业的结合,我是怎么看待毕业后 0 基础,如何选型如何切入到前端这个行业,等等等等吧,大家不要寄希望,我是烂尾王,哈哈哈。技术路漫漫且修远,吾将上下而求索,与各位共勉。

我应该会在简书、segmentfault 等平台进行发布,转载本文章请注明出处。

查看原文

赞 93 收藏 277 评论 27

姚雪 提出了问题 · 2019-04-03

用Js 做一个显示时间的效果

2.做一个上传的时间显示的效果,有几点
1),当天显示:分钟前,小时前
2),昨天发布的消息显示:昨天
3),前天天以及更早的截至到当年的消息显示:
4),去年以及更早的消息显示:*月 *号

关注 2 回答 1

姚雪 提出了问题 · 2019-04-03

用Js 做一个显示时间的效果

2.做一个上传的时间显示的效果,有几点
1),当天显示:分钟前,小时前
2),昨天发布的消息显示:昨天
3),前天天以及更早的截至到当年的消息显示:
4),去年以及更早的消息显示:*月 *号

关注 2 回答 1

姚雪 回答了问题 · 2019-04-03

解决小程序赋值问题

找到当前元素的索引值,设置值

关注 2 回答 1

姚雪 回答了问题 · 2019-04-03

a标签的下载功能失效,会打开文件

参考案例:
<span class="key">附件:${urlMap[itemKey]}</span>

$("button.download-url").click(function () {

    var url = $(this).val();
    //下载按钮的name属性值与a标签的id相同
    var name = $(this).attr("name");
    var id = "#" + name;
    $(id).attr({
        href: url,
        target: "_self"
    })[0].click();
})

关注 2 回答 2

姚雪 回答了问题 · 2019-04-03

为什么我的上面的border 看不见呢

给span加个属性:
span{

display:inline-block;

}
如果有帮到你请 upvote 或 采纳哦

关注 3 回答 3

姚雪 赞了文章 · 2019-02-18

中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)

引言

当下,正面临着近几年来的最严重的互联网寒冬,听得最多的一句话便是:相见于江湖~🤣。缩减HC、裁员不绝于耳,大家都是人心惶惶,年前如此,年后想必肯定又是一场更为惨烈的江湖厮杀。但博主始终相信,寒冬之中,人才更是尤为珍贵。只要有过硬的操作和装备,在逆风局下,同样也能来一波收割翻盘。

博主也是年前经历了一番厮杀,最终拿到多家大厂的 offer。在闭关修炼的过程中,自己整理出了一套面试秘籍供自己反复研究,后来给了多位有需要的兄台,均表示相当靠谱,理应在这寒冬之中回报于社会。于是决定花点精力整理成文,让大家能比较系统的反复学习,快速提升自己。

面试固然有技巧,但绝不是伪造与吹流弊,通过一段短时间沉下心来闭关修炼,出山收割,步入大厂,薪资翻番,岂不爽哉?🤓

修炼原则

想必大家很厌烦笔试和考察知识点。因为其实在平时实战中,讲究的是开发效率,很少会去刻意记下一些细节和深挖知识点,脑海中都是一些分散的知识点,无法系统性地关联成网,一直处于时曾相识的状态。不知道多少人和博主一样,至今每次写阻止冒泡都需要谷歌一番如何拼写。🤪。

以如此的状态,定然是无法在面试的战场上纵横的。其实面试就犹如考试,大家回想下高考之前所做的事,无非就是 理解系统性关联记忆。本秘籍的知识点较多,花点时间一个个理解并记忆后,自然也就融会贯通,无所畏惧。

由于本秘籍为了便于记忆,快速达到应试状态,类似于复习知识大纲。知识点会尽量的精简与提炼知识脉络,并不去展开深入细节,面面俱到。有兴趣或者有疑问的童鞋可以自行谷歌下对应知识点的详细内容。😋

CSS

1. 盒模型

页面渲染时,dom 元素所采用的 布局模型。可通过box-sizing进行设置。根据计算宽高的区域可分为:

  • content-box (W3C 标准盒模型)
  • border-box (IE 盒模型)
  • padding-box
  • margin-box

2. BFC

块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

IE下为 Layout,可通过 zoom:1 触发
  • 触发条件:

    • 根元素
    • positon: absolute/fixed
    • display: inline-block / table
    • float 元素
    • ovevflow !== visible
  • 规则:

    • 属于同一个 BFC 的两个相邻 Box 垂直排列
    • 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
    • BFC 中子元素不会超出他的包含块
    • BFC 的区域不会与 float 的元素区域重叠
    • 计算 BFC 的高度时,浮动子元素也参与计算
    • 文字层不会被浮动层覆盖,环绕于周围
  • 应用:

    • 阻止margin重叠
    • 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
    • 自适应两栏布局
    • 可以阻止元素被浮动元素覆盖

3.层叠上下文

元素提升为一个比较特殊的图层,在三维空间中 (z轴) 高出普通元素一等。

  • 触发条件

    • 根层叠上下文(html)
    • position
    • css3属性

      • flex
      • transform
      • opacity
      • filter
      • will-change
      • -webkit-overflow-scrolling
  • 层叠等级:层叠上下文在z轴上的排序

    • 在同一层叠上下文中,层叠等级才有意义
    • z-index的优先级最高

4. 居中布局

  • 水平居中

    • 行内元素: text-align: center
    • 块级元素: margin: 0 auto
    • absolute + transform
    • flex + justify-content: center
  • 垂直居中

    • line-height: height
    • absolute + transform
    • flex + align-items: center
    • table
  • 水平垂直居中

    • absolute + transform
    • flex + justify-content + align-items

5. 选择器优先级

  • !important > 行内样式 > #id > .class > tag > * > 继承 > 默认
  • 选择器 从右往左 解析

6.去除浮动影响,防止父级高度塌陷

  • 通过增加尾元素清除浮动

    • :after / <br> : clear: both
  • 创建父级 BFC
  • 父级设置高度

7.link 与 @import 的区别

  • link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css
  • 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载
  • @import需要 IE5 以上才能使用
  • link可以使用 js 动态引入,@import不行

8. CSS预处理器(Sass/Less/Postcss)

CSS预处理器的原理: 是将类 CSS 语言通过 Webpack 编译 转成浏览器可读的真正 CSS。在这层编译之上,便可以赋予 CSS 更多更强大的功能,常用功能:

  • 嵌套
  • 变量
  • 循环语句
  • 条件语句
  • 自动前缀
  • 单位转换
  • mixin复用

面试中一般不会重点考察该点,一般介绍下自己在实战项目中的经验即可~

9.CSS动画

  • transition: 过渡动画

    • transition-property: 属性
    • transition-duration: 间隔
    • transition-timing-function: 曲线
    • transition-delay: 延迟
    • 常用钩子: transitionend
  • animation / keyframes

    • animation-name: 动画名称,对应@keyframes
    • animation-duration: 间隔
    • animation-timing-function: 曲线
    • animation-delay: 延迟
    • animation-iteration-count: 次数

      • infinite: 循环动画
    • animation-direction: 方向

      • alternate: 反向播放
    • animation-fill-mode: 静止模式

      • forwards: 停止时,保留最后一帧
      • backwards: 停止时,回到第一帧
      • both: 同时运用 forwards / backwards
    • 常用钩子: animationend
  • 动画属性: 尽量使用动画属性进行动画,能拥有较好的性能表现

    • translate
    • scale
    • rotate
    • skew
    • opacity
    • color

经验

通常,CSS 并不是重点的考察领域,但这其实是由于现在国内业界对 CSS 的专注不够导致的,真正精通并专注于 CSS 的团队和人才并不多。因此如果能在 CSS 领域有自己的见解和经验,反而会为相当的加分和脱颖而出。

JavaScript

1. 原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
  • 构造函数: 可以通过new新建一个对象 的函数。
  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数

说了一大堆,大家可能有点懵逼,这里来举个栗子,以Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。

// 实例
const instance = new Object()

则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:

// 原型
const prototype = Object.prototype

这里我们可以来看出三者的关系:

实例.__proto__ === 原型

原型.constructor === 构造函数

构造函数.prototype === 原型

实例.constructorr === 构造函数

放大来看,我画了张图供大家彻底理解:

2.原型链:

原型链是由原型对象组成,每个对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。

  • 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined
  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。

3. 执行上下文(EC)

执行上下文可以简单理解为一个对象:

  • 它包含三个部分:

    • 变量对象(VO)
    • 作用域链(词法作用域)
    • this指向
  • 它的类型:

    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文
  • 代码执行过程:

    • 创建 全局上下文 (global EC)
    • 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
    • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
    • 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行

2.变量对象

变量对象,是执行上下文中的一部分,可以抽象为一种 数据作用域,其实也可以理解为就是一个简单的对象,它存储着该执行上下文中的所有 变量和函数声明(不包含函数表达式)

活动对象 (AO): 当变量对象所处的上下文为 active EC 时,称为活动对象。

3. 作用域

执行上下文中还包含作用域链。理解作用域之前,先介绍下作用域。作用域其实可理解为该上下文中声明的 变量和声明的作用范围。可分为 块级作用域函数作用域

特性:

  • 声明提前: 一个声明在函数体内都是可见的, 函数优先于变量
  • 非匿名自执行函数,函数变量为 只读 状态,无法修改
const foo = 1
(function foo() {
    foo = 10  // 由于foo在函数中只为可读,因此赋值无效
    console.log(foo)
}()) 

// 结果打印:  ƒ foo() { foo = 10 ; console.log(foo) }

4.作用域链

我们知道,我们可以在执行上下文中访问到父级甚至全局的变量,这便是作用域链的功劳。作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,因此我们便能通过作用域链访问到父级里声明的变量或者函数。

  • 由两部分组成:

    • [[scope]]属性: 指向父级变量对象和作用域链,也就是包含了父级的[[scope]]AO
    • AO: 自身活动对象

如此 [[scopr]]包含[[scope]],便自上而下形成一条 链式作用域

5. 闭包

闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。

  • 闭包会产生一个很经典的问题:

    • 多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
  • 解决:

    • 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
    • 使用setTimeout包裹,通过第三个参数传入
    • 使用 块级作用域,让变量成为自己上下文的属性,避免共享

6. script 引入方式:

  • html 静态<script>引入
  • js 动态插入<script>
  • <script defer>: 异步加载,元素解析完成后执行
  • <script async>: 异步加载,与元素渲染并行执行

7. 对象的拷贝

  • 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

    • Object.assign
    • 展开运算符(...)
  • 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

    • JSON.parse(JSON.stringify(obj)): 性能最快

      • 具有循环引用的对象时,报错
      • 当值为函数或undefined时,无法拷贝
    • 递归进行逐一赋值

8. new运算符的执行过程

  • 新生成一个对象
  • 链接到原型: obj.__proto__ = Con.prototype
  • 绑定this: apply
  • 返回新对象

9. instanceof原理

能在实例的 原型对象链 中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:

// __proto__: 代表原型对象链
instance.[__proto__...] === instance.constructor.prototype

// return true

10. 代码的复用

当你发现任何代码开始写第二遍时,就要开始考虑如何复用。一般有以下的方式:

  • 函数封装
  • 继承
  • 复制extend
  • 混入mixin
  • 借用apply/call

11. 继承

在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

  • 最优化: 圣杯模式
var inherit = (function(c,p){
    var F = function(){};
    return function(c,p){
        F.prototype = p.prototype;
        c.prototype = new F();
        c.uber = p.prototype;
        c.prototype.constructor = c;
    }
})();
  • 使用 ES6 的语法糖 class / extends

12. 类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

  • -、*、/、% :一律转换成数值后计算
  • +:

    • 数字 + 字符串 = 字符串, 运算顺序是从左到右
    • 数字 + 对象, 优先调用对象的valueOf -> toString
    • 数字 + boolean/null = 数字
    • 数字 + undefined == NaN
  • [1].toString() === '1'
  • {}.toString() === '[object object]'
  • NaN !== NaN+undefined === NaN

13. 类型判断

判断 Target 的类型,单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:

  • 基本类型(null): 使用 String(null)
  • 基本类型(string / number / boolean / undefined) + function: 直接使用 typeof即可
  • 其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断

很稳的判断封装:

let class2type = {}
'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) 

function type(obj) {
    if (obj == null) return String(obj)
    return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}

14. 模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。

  • 分类:

    • es6: import / exports
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • requireimport的区别

    • require支持 动态导入import不支持,正在提案 (babel 下可支持)
    • require同步 导入,import属于 异步 导入
    • require值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化

15. 防抖与节流

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

  • 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce(fn, wait, immediate) {
    let timer = null

    return function() {
        let args = arguments
        let context = this

        if (immediate && !timer) {
            fn.apply(context, args)
        }

        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}
  • 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
function throttle(fn, wait, immediate) {
    let timer = null
    let callNow = true
    
    return function() {
        let context = this,
            args = arguments

        if (callNow) {
            fn.apply(context, args)
            callNow = false
        }

        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null
            }, wait)
        }
    }
}

16. 函数执行改变this

由于 JS 的设计原理: 在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this

因此要明白 this 指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如:

  • obj.fn(),便是 obj 调用了函数,既函数中的 this === obj
  • fn(),这里可以看成 window.fn(),因此 this === window

但这种机制并不完全能满足我们的业务需求,因此提供了三种方式可以手动修改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

17. ES6/ES7

由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。

  • 声明

    • let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
    • const: 声明常量,无法修改
  • 解构赋值
  • class / extend: 类声明与继承
  • Set / Map: 新的数据结构
  • 异步解决方案:

    • Promise的使用与实现
    • generator:

      - `yield`: 暂停代码 
      - `next()`: 继续执行代码
      
function* helloWorld() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

const generator = helloWorld();

generator.next()  // { value: 'hello', done: false }

generator.next()  // { value: 'world', done: false }

generator.next()  // { value: 'ending', done: true }

generator.next()  // { value: undefined, done: true }

- `await / async`: 是`generator`的语法糖, babel中是基于`promise`实现。

```js
async function getUserByAsync(){
   let user = await fetchUser();
   return user;
}

const user = await getUserByAsync()
console.log(user)
``` 

18. AST

抽象语法树 (Abstract Syntax Tree),是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:

function square(n){
    return n * n
}

通过解析转化成的AST如下图:

19. babel编译原理

  • babylon 将 ES6/ES7 代码解析成 AST
  • babel-traverse 对 AST 进行遍历转译,得到新的 AST
  • 新 AST 通过 babel-generator 转换成 ES5

20. 函数柯里化

在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。

const add = function add(x) {
    return function (y) {
        return x + y
    }
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

21. 数组(array)

  • map: 遍历数组,返回回调返回值组成的新数组
  • forEach: 无法break,可以用try/catchthrow new Error来停止
  • filter: 过滤
  • some: 有一项返回true,则整体为true
  • every: 有一项返回false,则整体为false
  • join: 通过指定连接符生成字符串
  • push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项
  • unshift / shift: 头部推入和弹出,改变原数组,返回操作项
  • sort(fn) / reverse: 排序与反转,改变原数组
  • concat: 连接数组,不影响原数组, 浅拷贝
  • slice(start, end): 返回截断后的新数组,不改变原数组
  • splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
  • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
  • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
  • 数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
  • 数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
arr.prototype.flat = function() {
    this.toString().split(',').map(item => +item )
}

浏览器

1. 跨标签页通讯

不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:

  • 通过父页面window.open()和子页面postMessage

    • 异步下,通过 window.open('about: blank')tab.location.href = '*'
  • 设置同域下共享的localStorage与监听window.onstorage

    • 重复写入相同的值无法触发
    • 会受到浏览器隐身模式等的限制
  • 设置共享cookie与不断轮询脏检查(setInterval)
  • 借助服务端或者中间层实现

2. 浏览器架构

  • 用户界面
  • 主进程
  • 内核

    • 渲染引擎
    • JS 引擎

      • 执行栈
    • 事件触发线程

      • 消息队列

        • 微任务
        • 宏任务
    • 网络异步线程
    • 定时器线程

3. 浏览器下事件循环(Event Loop)

事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

  • 微任务 microtask(jobs): promise / ajax / Object.observe
  • 宏任务 macrotask(task): setTimout / script / IO / UI Rendering

4. 从输入 url 到展示的过程

  • DNS 解析
  • TCP 三次握手
  • 发送请求,分析 url,设置请求报文(头,主体)
  • 服务器返回请求的文件 (html)
  • 浏览器渲染

    • HTML parser --> DOM Tree

      • 标记化算法,进行元素状态的标记
      • dom 树构建
    • CSS parser --> Style Tree

      • 解析 css 代码,生成样式树
    • attachment --> Render Tree

      • 结合 dom树 与 style树,生成渲染树
    • layout: 布局
    • GPU painting: 像素绘制页面

5. 重绘与回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
  • 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:

    • 页面初次渲染
    • 浏览器窗口大小改变
    • 元素尺寸、位置、内容发生改变
    • 元素字体大小变化

      • 添加或者删除可见的 dom 元素
    • 激活 CSS 伪类(例如::hover)
    • 查询某些属性或调用某些方法

      - clientWidth、clientHeight、clientTop、clientLeft
      - offsetWidth、offsetHeight、offsetTop、offsetLeft
      - scrollWidth、scrollHeight、scrollTop、scrollLeft
      - getComputedStyle()
      - getBoundingClientRect()
      - scrollTo()
      

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

最佳实践:

  • css

    • 避免使用table布局
    • 将动画效果应用到position属性为absolutefixed的元素上
  • javascript

    • 避免频繁操作样式,可汇总后统一 一次修改
    • 尽量使用class进行样式修改
    • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
    • 极限优化时,修改样式可将其display: none后修改
    • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

6. 存储

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
  • 持久性存储,可以分为 浏览器端 与 服务器端

    • 浏览器:

      • cookie: 通常用于存储用户身份,登录状态等

        • http 中自动携带, 体积上限为 4K, 可自行设置过期时间
      • localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5M
      • indexDB
    • 服务器:

      • 分布式缓存 redis
      • 数据库

7. Web Worker

现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信。

基本用法:

// 创建 worker
const worker = new Worker('work.js');

// 向主进程推送消息
worker.postMessage('Hello World');

// 监听主进程来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}

限制:

  • 同源限制
  • 无法使用 document / window / alert / confirm
  • 无法加载本地资源

8. V8垃圾回收机制

垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间老生代空间

  • 新生代空间: 用于存活较短的对象

    • 又分成两个空间: from 空间 与 to 空间
    • Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法

      • 存活的对象从 from space 转移到 to space
      • 清空 from space
      • from space 与 to space 互换
      • 完成一次新生代GC
  • 老生代空间: 用于存活时间较长的对象

    • 从 新生代空间 转移到 老生代空间 的条件

      • 经历过一次以上 Scavenge GC 的对象
      • 当 to space 体积超过25%
    • 标记清除算法: 标记存活的对象,未被标记的则被释放

      • 增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能
      • 并发标记(最新技术): 不阻塞 js 执行
    • 压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决 内存的碎片化

9. 内存泄露

  • 意外的全局变量: 无法被回收
  • 定时器: 未被正确关闭,导致所引用的外部变量无法被释放
  • 事件监听: 没有正确销毁 (低版本浏览器可能出现)
  • 闭包: 会导致父级中的变量无法被释放
  • dom 引用: dom 元素被删除时,内存中的引用未被正确清空

可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化情况,找出异常点。

服务端与网络

1. http/https 协议

  • 1.0 协议缺陷:

    • 无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
    • head of line blocking: 线头阻塞,导致请求之间互相影响
  • 1.1 改进:

    • 长连接(默认 keep-alive),复用
    • host 字段指定对应的虚拟站点
    • 新增功能:

      • 断点续传
      • 身份认证
      • 状态管理
      • cache 缓存

        • Cache-Control
        • Expires
        • Last-Modified
        • Etag
  • 2.0:

    • 多路复用
    • 二进制分帧层: 应用层和传输层之间
    • 首部压缩
    • 服务端推送
  • https: 较为安全的网络传输协议

    • 证书(公钥)
    • SSL 加密
    • 端口 443
  • TCP:

    • 三次握手
    • 四次挥手
    • 滑动窗口: 流量控制
    • 拥塞处理

      • 慢开始
      • 拥塞避免
      • 快速重传
      • 快速恢复
  • 缓存策略: 可分为 强缓存协商缓存

    • Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires
    • 当缓存已经过期时,使用协商缓存

      • 唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,
      • 最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)

        • 如果一致,则直接返回 304 通知浏览器使用缓存
        • 如不一致,则服务端返回新的资源
    • Last-Modified 缺点:

      • 周期性修改,但内容未变时,会导致缓存失效
      • 最小粒度只到 s, s 以内的改动无法检测到
    • Etag 的优先级高于 Last-Modified

2. 常见状态码

  • 1xx: 接受,继续处理
  • 200: 成功,并返回数据
  • 201: 已创建
  • 202: 已接受
  • 203: 成为,但未授权
  • 204: 成功,无内容
  • 205: 成功,重置内容
  • 206: 成功,部分内容
  • 301: 永久移动,重定向
  • 302: 临时移动,可使用原有URI
  • 304: 资源未修改,可使用缓存
  • 305: 需代理访问
  • 400: 请求语法错误
  • 401: 要求身份认证
  • 403: 拒绝请求
  • 404: 资源不存在
  • 500: 服务器错误

3. get / post

  • get: 缓存、请求长度受限、会被历史保存记录

    • 无副作用(不修改资源),幂等(请求次数与资源无关)的场景
  • post: 安全、大数据、更多编码类型

两者详细对比如下图:

4. Websocket

Websocket 是一个 持久化的协议, 基于 http , 服务端可以 主动 push

  • 兼容:

    • FLASH Socket
    • 长轮询: 定时发送 ajax
    • long poll: 发送 --> 有消息时再 response
  • new WebSocket(url)
  • ws.onerror = fn
  • ws.onclose = fn
  • ws.onopen = fn
  • ws.onmessage = fn
  • ws.send()

5. TCP三次握手

建立连接前,客户端和服务端需要通过握手来确认对方:

  • 客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认
  • 服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态
  • 客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态

6. TCP四次挥手

  • 客户端 -- FIN --> 服务端, FIN—WAIT
  • 服务端 -- ACK --> 客户端, CLOSE-WAIT
  • 服务端 -- ACK,FIN --> 客户端, LAST-ACK
  • 客户端 -- ACK --> 服务端,CLOSED

7. Node 的 Event Loop: 6个阶段

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调
  • I/O 阶段: 执行上轮循环残流的callback
  • idle, prepare
  • poll: 等待回调

      1. 执行回调
      1. 执行定时器
      • 如有到期的setTimeout / setInterval, 则返回 timer 阶段
      • 如有setImmediate,则前往 check 阶段
  • check

    • 执行setImmediate
  • close callbacks

跨域

  • JSONP: 利用<script>标签不受跨域限制的特点,缺点是只能支持 get 请求
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
  • 设置 CORS: Access-Control-Allow-Origin:*
  • postMessage

安全

  • XSS攻击: 注入恶意代码

    • cookie 设置 httpOnly
    • 转义页面上的输入内容和输出内容
  • CSPF: 跨站请求伪造,防护:

    • get 不修改数据
    • 不被第三方网站访问到用户的 cookie
    • 设置白名单,不被第三方网站请求
    • 请求校验

框架:Vue

1. nextTick

在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态

  • 新版本中默认是mincrotasks, v-on中会使用macrotasks
  • macrotasks任务的实现:

    • setImmediate / MessageChannel / setTimeout

2. 生命周期

  • _init_

    • initLifecycle/Event,往vm上挂载各种属性
    • callHook: beforeCreated: 实例刚创建
    • initInjection/initState: 初始化注入和 data 响应性
    • created: 创建完成,属性已经绑定, 但还未生成真实dom
    • 进行元素的挂载: $el / vm.$mount()
    • 是否有template: 解析成render function

      • *.vue文件: vue-loader会将<template>编译成render function
    • beforeMount: 模板编译/挂载之前
    • 执行render function,生成真实的dom,并替换到dom tree
    • mounted: 组件已挂载
  • update:

    • 执行diff算法,比对改变是否需要触发UI更新
    • flushScheduleQueue

      • watcher.before: 触发beforeUpdate钩子 - watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI
    • 触发updated钩子: 组件已更新
  • actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活
  • destroy:

    • beforeDestroy: 销毁开始
    • 销毁自身且递归销毁子组件以及事件监听

      • remove(): 删除节点
      • watcher.teardown(): 清空依赖
      • vm.$off(): 解绑监听
    • destroyed: 完成后触发钩子

上面是vue的声明周期的简单梳理,接下来我们直接以代码的形式来完成vue的初始化


new Vue({})

// 初始化Vue实例
function _init() {
     // 挂载属性
    initLifeCycle(vm) 
    // 初始化事件系统,钩子函数等
    initEvent(vm) 
    // 编译slot、vnode
    initRender(vm) 
    // 触发钩子
    callHook(vm, 'beforeCreate')
    // 添加inject功能
    initInjection(vm)
    // 完成数据响应性 props/data/watch/computed/methods
    initState(vm)
    // 添加 provide 功能
    initProvide(vm)
    // 触发钩子
    callHook(vm, 'created')
        
     // 挂载节点
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }
}

// 挂载节点实现
function mountComponent(vm) {
     // 获取 render function
    if (!this.options.render) {
        // template to render
        // Vue.compile = compileToFunctions
        let { render } = compileToFunctions() 
        this.options.render = render
    }
    // 触发钩子
    callHook('beforeMounte')
    // 初始化观察者
    // render 渲染 vdom, 
    vdom = vm.render()
    // update: 根据 diff 出的 patchs 挂载成真实的 dom 
    vm._update(vdom)
    // 触发钩子  
    callHook(vm, 'mounted')
}

// 更新节点实现
funtion queueWatcher(watcher) {
    nextTick(flushScheduleQueue)
}

// 清空队列
function flushScheduleQueue() {
     // 遍历队列中所有修改
    for(){
        // beforeUpdate
        watcher.before()
         
        // 依赖局部更新节点
        watcher.update() 
        callHook('updated')
    }
}

// 销毁实例实现
Vue.prototype.$destory = function() {
     // 触发钩子
    callHook(vm, 'beforeDestory')
    // 自身及子节点
    remove() 
    // 删除依赖
    watcher.teardown() 
    // 删除监听
    vm.$off() 
    // 触发钩子
    callHook(vm, 'destoryed')
}

3. 数据响应(数据劫持)

看完生命周期后,里面的watcher等内容其实是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher )依赖收集器( Dep ),其核心是 defineProperty这个方法,它可以 重写属性的 get 与 set 方法,从而完成监听数据的改变。

  • Observe (观察者)观察 props 与 state

    • 遍历 props 与 state,对每个属性创建独立的监听器( watcher )
  • 使用 defineProperty 重写每个属性的 get/set(defineReactive

    • get: 收集依赖

      • Dep.depend()

        • watcher.addDep()
    • set: 派发更新

      • Dep.notify()
      • watcher.update()
      • queenWatcher()
      • nextTick
      • flushScheduleQueue
      • watcher.run()
      • updateComponent()

大家可以先看下面的数据相应的代码实现后,理解后就比较容易看懂上面的简单脉络了。

let data = {a: 1}
// 数据响应性
observe(data)

// 初始化观察者
new Watcher(data, 'name', updateComponent)
data.a = 2

// 简单表示用于数据更新后的操作
function updateComponent() {
    vm._update() // patchs
}

// 监视对象
function observe(obj) {
     // 遍历对象,使用 get/set 重新定义对象的每个属性值
    Object.keys(obj).map(key => {
        defineReactive(obj, key, obj[key])
    })
}

function defineReactive(obj, k, v) {
    // 递归子属性
    if (type(v) == 'object') observe(v)
    
    // 新建依赖收集器
    let dep = new Dep()
    // 定义get/set
    Object.defineProperty(obj, k, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
              // 当有获取该属性时,证明依赖于该对象,因此被添加进收集器中
            if (Dep.target) {
                dep.addSub(Dep.target)
            }
            return v
        },
        // 重新设置值时,触发收集器的通知机制
        set: function reactiveSetter(nV) {
            v = nV
            dep.nofify()
        },
    })
}

// 依赖收集器
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    notify() {
        this.subs.map(sub => {
            sub.update()
        })
    }
}

Dep.target = null

// 观察者
class Watcher {
    constructor(obj, key, cb) {
        Dep.target = this
        this.cb = cb
        this.obj = obj
        this.key = key
        this.value = obj[key]
        Dep.target = null
    }
    addDep(Dep) {
        Dep.addSub(this)
    }
    update() {
        this.value = this.obj[this.key]
        this.cb(this.value)
    }
    before() {
        callHook('beforeUpdate')
    }
}

4. virtual dom 原理实现

  • 创建 dom 树
  • 树的diff,同层对比,输出patchs(listDiff/diffChildren/diffProps)

    • 没有新的节点,返回
    • 新的节点tagNamekey不变, 对比props,继续递归遍历子树

      • 对比属性(对比新旧属性列表):

        • 旧属性是否存在与新属性列表中
        • 都存在的是否有变化
        • 是否出现旧列表中没有的新属性
    • tagNamekey值变化了,则直接替换成新节点
  • 渲染差异

    • 遍历patchs, 把需要更改的节点取出来
    • 局部更新dom
// diff算法的实现
function diff(oldTree, newTree) {
     // 差异收集
    let pathchs = {}
    dfs(oldTree, newTree, 0, pathchs)
    return pathchs
}

function dfs(oldNode, newNode, index, pathchs) {
    let curPathchs = []
    if (newNode) {
        // 当新旧节点的 tagName 和 key 值完全一致时
        if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
              // 继续比对属性差异
            let props = diffProps(oldNode.props, newNode.props)
            curPathchs.push({ type: 'changeProps', props })
            // 递归进入下一层级的比较
            diffChildrens(oldNode.children, newNode.children, index, pathchs)
        } else {
              // 当 tagName 或者 key 修改了后,表示已经是全新节点,无需再比
            curPathchs.push({ type: 'replaceNode', node: newNode })
        }
    }

     // 构建出整颗差异树
    if (curPathchs.length) {
            if(pathchs[index]){
                pathchs[index] = pathchs[index].concat(curPathchs)
            } else {
                pathchs[index] = curPathchs
            }
    }
}

// 属性对比实现
function diffProps(oldProps, newProps) {
    let propsPathchs = []
    // 遍历新旧属性列表
    // 查找删除项
    // 查找修改项
    // 查找新增项
    forin(olaProps, (k, v) => {
        if (!newProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'remove', prop: k })
        } else {
            if (v !== newProps[k]) {
                propsPathchs.push({ type: 'change', prop: k , value: newProps[k] })
            }
        }
    })
    forin(newProps, (k, v) => {
        if (!oldProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'add', prop: k, value: v })
        }
    })
    return propsPathchs
}

// 对比子级差异
function diffChildrens(oldChild, newChild, index, pathchs) {
        // 标记子级的删除/新增/移动
    let { change, list } = diffList(oldChild, newChild, index, pathchs)
    if (change.length) {
        if (pathchs[index]) {
            pathchs[index] = pathchs[index].concat(change)
        } else {
            pathchs[index] = change
        }
    }

     // 根据 key 获取原本匹配的节点,进一步递归从头开始对比
    oldChild.map((item, i) => {
        let keyIndex = list.indexOf(item.key)
        if (keyIndex) {
            let node = newChild[keyIndex]
            // 进一步递归对比
            dfs(item, node, index, pathchs)
        }
    })
}

// 列表对比,主要也是根据 key 值查找匹配项
// 对比出新旧列表的新增/删除/移动
function diffList(oldList, newList, index, pathchs) {
    let change = []
    let list = []
    const newKeys = getKey(newList)
    oldList.map(v => {
        if (newKeys.indexOf(v.key) > -1) {
            list.push(v.key)
        } else {
            list.push(null)
        }
    })

    // 标记删除
    for (let i = list.length - 1; i>= 0; i--) {
        if (!list[i]) {
            list.splice(i, 1)
            change.push({ type: 'remove', index: i })
        }
    }

    // 标记新增和移动
    newList.map((item, i) => {
        const key = item.key
        const index = list.indexOf(key)
        if (index === -1 || key == null) {
            // 新增
            change.push({ type: 'add', node: item, index: i })
            list.splice(i, 0, key)
        } else {
            // 移动
            if (index !== i) {
                change.push({
                    type: 'move',
                    form: index,
                    to: i,
                })
                move(list, index, i)
            }
        }
    })

    return { change, list }
}

5. Proxy 相比于 defineProperty 的优势

  • 数组变化也能监听到
  • 不需要深度遍历监听
let data = { a: 1 }
let reactiveData = new Proxy(data, {
    get: function(target, name){
        // ...
    },
    // ...
})

6. vue-router

  • mode

    • hash
    • history
  • 跳转

    • this.$router.push()
    • <router-link to=""></router-link>
  • 占位

    • <router-view></router-view>

7. vuex

  • state: 状态中心
  • mutations: 更改状态
  • actions: 异步更改状态
  • getters: 获取状态
  • modules: 将state分成多个modules,便于管理

算法

其实算法方面在前端的实际项目中涉及得并不多,但还是需要精通一些基础性的算法,一些公司还是会有这方面的需求和考核,建议大家还是需要稍微准备下,这属于加分题。

1. 五大算法

  • 贪心算法: 局部最优解法
  • 分治算法: 分成多个小模块,与原问题性质相同
  • 动态规划: 每个状态都是过去历史的一个总结
  • 回溯法: 发现原先选择不优时,退回重新选择
  • 分支限界法

2. 基础排序算法

  • 冒泡排序: 两两比较
    function bubleSort(arr) {
        var len = arr.length;
        for (let outer = len ; outer >= 2; outer--) {
            for(let inner = 0; inner <=outer - 1; inner++) {
                if(arr[inner] > arr[inner + 1]) {
                    [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
                }
            }
        }
        return arr;
    }
  • 选择排序: 遍历自身以后的元素,最小的元素跟自己调换位置
function selectSort(arr) {
    var len = arr.length;
    for(let i = 0 ;i < len - 1; i++) {
        for(let j = i ; j<len; j++) {
            if(arr[j] < arr[i]) {
                [arr[i],arr[j]] = [arr[j],arr[i]];
            }
        }
    }
    return arr
}
  • 插入排序: 即将元素插入到已排序好的数组中
function insertSort(arr) {
    for(let i = 1; i < arr.length; i++) {  //外循环从1开始,默认arr[0]是有序段
        for(let j = i; j > 0; j--) {  //j = i,将arr[j]依次插入有序段中
            if(arr[j] < arr[j-1]) {
                [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
            } else {
                break;
            }
        }
    }
    return arr;
}

3. 高级排序算法

  • 快速排序

    • 选择基准值(base),原数组长度减一(基准值),使用 splice
    • 循环原数组,小的放左边(left数组),大的放右边(right数组);
    • concat(left, base, right)
    • 递归继续排序 left 与 right
function quickSort(arr) {
    if(arr.length <= 1) {
        return arr;  //递归出口
    }
    var left = [],
        right = [],
        current = arr.splice(0,1); 
    for(let i = 0; i < arr.length; i++) {
        if(arr[i] < current) {
            left.push(arr[i])  //放在左边
        } else {
            right.push(arr[i]) //放在右边
        }
    }
    return quickSort(left).concat(current,quickSort(right));
}
  • 希尔排序:不定步数的插入排序,插入排序
  • 口诀: 插冒归基稳定,快选堆希不稳定

稳定性: 同大小情况下是否可能会被交换位置, 虚拟dom的diff,不稳定性会导致重新渲染;

4. 递归运用(斐波那契数列): 爬楼梯问题

初始在第一级,到第一级有1种方法(s(1) = 1),到第二级也只有一种方法(s(2) = 1), 第三级(s(3) = s(1) + s(2))

function cStairs(n) {
    if(n === 1 || n === 2) {
        return 1;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}

5. 数据树

  • 二叉树: 最多只有两个子节点

    • 完全二叉树
    • 满二叉树

      • 深度为 h, 有 n 个节点,且满足 n = 2^h - 1
  • 二叉查找树: 是一种特殊的二叉树,能有效地提高查找效率

    • 小值在左,大值在右
    • 节点 n 的所有左子树值小于 n,所有右子树值大于 n

  • 遍历节点

    • 前序遍历

        1. 根节点
        1. 访问左子节点,回到 1
        1. 访问右子节点,回到 1
    • 中序遍历

        1. 先访问到最左的子节点
        1. 访问该节点的父节点
        1. 访问该父节点的右子节点, 回到 1
    • 后序遍历

        1. 先访问到最左的子节点
        1. 访问相邻的右节点
        1. 访问父节点, 回到 1
  • 插入与删除节点

6. 天平找次品

有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少需要称多少次能保证一定找到假币?

  • 三等分算法:

      1. 将硬币分成3组,随便取其中两组天平称量
      • 平衡,假币在未上称的一组,取其回到 1 继续循环
      • 不平衡,假币在天平上较轻的一组, 取其回到 1 继续循环

结语

由于精力时间及篇幅有限,这篇就先写到这。大家慢慢来不急。。🤪。下篇打算准备以下内容,我也得补补课先:

  • Webpack相关

    • 原理
    • Loader
    • Plugin
  • 项目性能优化

    • 首屏渲染优化
    • 用户体验优化
    • webpack 性能优化
  • Hybrid 与 Webview

    • webview 加载过程
    • bridge 原理
    • hybrid app 经验
  • 框架: React

在面试中,很多领域并没有真正的答案,能回答到什么样的深度,还是得靠自己真正的去使用和研究。知识面的广度与深度应该并行,尽量的拓张自己的领域,至少都有些基础性的了解,在被问到的时候可以同面试官唠嗑两句,然后在自己喜欢的领域,又有着足够深入的研究,让面试官觉得你是这方面的专家。

知识大纲还在不断的完善和修正,由于也是精力时间有限,我会慢慢补充后面列出来的部分。当然,我也是在整理中不断的学习,也希望大家能一起参与进来,要补充或修正的地方麻烦赶紧提出。另外,刚新建了个公众号,想作为大家交流和分享的地方,有兴趣想法的童鞋联系我哈~~😉

Tips:
头条招前端,内推的童鞋赶紧死命找我!
博主写得很辛苦,感恩 github。😚

查看原文

赞 448 收藏 354 评论 13

姚雪 关注了专栏 · 2019-02-18

tayde的专栏

收纳自己的一些研究性文章

关注 1122

姚雪 发布了文章 · 2018-12-29

git 提交代码报错误

图片描述

解决办法:在host文件中添加 192.168.2.25 github.com ,不要忘记上面要加一行注释#-------
看图:
这样就可以解决了 在正常push你的代码到分支就可以了 
成功!!

步骤1、图片描述
步骤2、 图片描述

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 11 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-09-13
个人主页被 644 人浏览