评论走起

杭州17届前端期待加入一个更好的团队

长文慎入哈,但是有酒有故事,小板凳我都给你搬好啦,快坐下! 故事开始咯~ 本来想等过完年回来再换工作的,但是前段时间有猎头在简书私信我,说看我的文章写得还行,问我想不想换工作。 然后我就更新了简历,发过去了,邀请我面试,但是这家公司在北京 /西安,后来就没去,但是故事就此开始了,这反而促使我走上了换工作的道路。 自己从大三暑假实习到现在工作半年多,接近快1.5+年的时间,一门心思扎到前端开发领域,高强度式的工作和学习,买书啃书,写代码看代码,写博客看博客,提问题回答问题,投入了几乎是大一到大三学习时间总和的时间学习前端开发。 最近阴差阳错开始笔试,面试,虽然都没怎么准备,但是很轻松就收获了一家阿里系公司,一家浙大系公司的offer,再加上昨天被第三家面试官的赞赏,之前的妄自菲薄一下烟消云散。 由于自己这次换工作,希望能够在一家公司呆至少2年以上,非常希望能和一帮对前端技术有见解有思考有追求的小伙伴们,一起去探索钻研有趣的前端世界。所以我决定尝试更多的面试,加入到一个更适合自己的团队。 同时也希望通过这篇面试分享,能够给和我一样努力的前端小伙伴们一些鼓励,也希望能够把最真实的自己展现给大家。 offer - 1 天道酬勤,付出终有回报 拒绝了第一家主动找到我的公司后,又有家阿里系创业公司在 BOSS 直聘里私信我,然后抱着测试水平的心态面了。 现场笔试不怎么样,但是他们看到我简书文章还算可以,就让我重做了一遍题目: 一些特别棒的面试题[0], 最后一道单词与字符串匹配的题的拓展,都是面试官一步步引导我去深入的,感觉学习到很多。 没想到结果是比较让人惊喜的,前辈说我的学习能力比较强,所以愿意给我这个机会,给的薪资比现在高 2K,关键是听说有许多项目可以做,据说面试我的老板以前也是在阿里的技术专家。 贴一下面试题和我的回答。 1.说一下你熟悉的设计模式 2.说一下你理解的模块机制 3.MVVM原理 4.最熟悉的框架路由机制 5.状态管理 6.统计字符串中单词出现次数 1.说一下你熟悉的设计模式 我最熟悉的设计模式:工厂模式(ES5),组件设计模式(ES6) 工厂模式(ES5,基于prototype。此例中基类Base,子类Factory) [pre] var Factory = function () { if(!(this instanceof Factory)){ return new Factory(); } } Factory.prototype = Object.assign(new Base(), { version: '0.0.1', defaultOption:{ title:'标题' }, init:function (cfg) { this.title = cfg.title || ''; this.currentOption = Object.assign(this.defaultOption,{ //... }) }, render: function () { var option = this.currentOption; this.chart.setOption(option); }, showTitle: function () { this._showTitle(); } }) [/pre] 组件设计模式(ES6,基于class,方便继承和初始化,也是React组件的推荐写法,我比较喜欢。此例中父类Compnent,子类Retrive) [pre] class Retrive extends Component { constructor (props) { super(props); this.state = { name:'' }; this.getRemoteData = this.getRemoteData.bind(this); } getRemoteData (data) { this.state.retriveResult = data; } render(){ return ( <div className="Retrive"> <Button name="search" onClick={this.getRemoteData}>查询</Button> </div> ); } } [/pre] 2.说一下你理解的模块机制 AMD: 异步模块加载规范。 a.js,定义一个依赖jQuery和echrts的组件。 [pre] define(['jquery', 'echarts'], function ($, echarts) { var AMD = function(){} AMD.prototype = { title:'', foo: function(){}//AMD类或者继承AMD类的子类的属性 } function bar(){}//返回,公共属性 function baz(){} //未返回,私有属性 return { main:AMD, bar: bar } }); 如果b.js依赖a.js,可以这样 define(['./a'], function (a) { //调用构造函数,foo var instance_amd = new a.main(); instance_amd.foo() //调用bar a.bar() }); [/pre] ES6 modules: 和python的包机制很类似,导入import,导出export。 1.场景:vue,react推荐机制,需要babel转义成es5以兼容浏览器。 2.关于import...(from...) ①.import...from...的from命令后面可以跟很多路径格式,若只给出vue,axios这样的包名,则会自动到node_modules中加载;若给出相对路径及文件前缀,则到指定位置寻找。 ②.可以加载各种各样的文件:.js、.vue、.less等等。 ③.可以省略掉from直接引入。 3.关于export ①.导出的可以是对象,表达式,函数,类 ②.导出是为了让别人导入 4.言外话:使用es6的话,有一个特别好的规范去遵守,airbnb的es6规范(https://github.com/airbnb/javascript) CommonJS:nodejs中使用较多,关键词是require,没写过node包,只引用过别人的模块,所以内部实现原理不是很清楚。 3.MVVM原理 MVVM是一种软件架构模式,MVVM有助于前后端分离。 View:视图层,粗略理解为DOM。 Model:与数据库对应的model,一般为json格式,作为req的body通过http(s)与数据库实现通信。 ViewModel:View与Model通过ViewModel实现双向绑定。 核心是提供对View和ViewModel的双向数据绑定,这样使得ViewModel的改变View立即变化,MVVM在前端的实现有:angular,vue,react。 vue中的常用数据双向绑定。 view:{{message}} viewModel v-model="message" model:message <div id="app-6"> <p>{{ message }}</p> <input v-model="message"> </div> var app6 = new Vue({ el: '#app-6', data: { message: 'Hello Vue!' } }) 单文件组件中的话,就多了一个用html5的template标签将view和viewModel包裹起来,model部分停留在script标签部分。 <template> view viewModel </tamplate> <script> model </script> <styles> 为了让view好看点 </styles> react的话,我在使用的过程中,没有听说过双向绑定的东西,对redux reducers推荐写为纯函数印象深刻,纯函数的话,感觉应该有点单项数据流的意思。 既然说到框架了,说一个最让我感觉有趣的点,那就是组件间的通信,对于简单组件,只涉及父子级别的通信的,vue使用on emit的方式,react使用props。对于复杂级别通信,爷爷父亲儿子孙子等等时,vue推荐使用vuex,react推荐使用redux,统一的全局状态树用来做状态管理非常好,可以使得逻辑非常清晰。vue项目文件结构研究不深,react的项目文件结构的话,presentational和containers的设计方法感觉非常有道理,一个负责视图一个负责数据,非常清爽。 4.最熟悉的框架路由机制 vue路由依赖:vue-router 通过组合组件来组成单页应用程序,只需要将组件映射到路由即可。 前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。 需要注意2种模式的区别:hash模式和history模式,hash模式会在后面加一个很丑的#,可以开启history去掉。 hash模式原理:它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。hash可以理解为锚点,例如./index.html/#/foo,hash值为#/foo,这样不会跳转页面。就相当于统一页面的不同锚点,页面间跳转与 ./index.html/#foo到./index.html/#bar类似。 ./store/index.js import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/common', name: 'common', component: Common } ] 路由层面还会包括嵌套路由,动态路由以及重定向,相当于自己模仿浏览器请求然后服务器响应模式,其实不涉及向后端请求,仅在浏览器就可以实现页面跳转,前段时间我做的用户权限控制就用到了vue-router,相比MVC结构下的后端路由,清晰了不少,这样后端只要负责路由编写api就好。 5.状态管理 下面是我在用vuex做项目时的一些思考,简单修改了一下,也添加了一些关于redux的思考。 vuex state,前端data view,前端DOM actions,用户操作,引起data变化从而导致DOM变化。 多个组件(视图)共享状态:通俗来讲,就是多个组件间会通信时,导致从后端拿来的数据发生变化,当组件较多时,如果兄弟组件间的通信都依赖父组件进行通信,会导致组件间的耦合非常高,从而导致项目逻辑混乱,难以维护。 多个组件(视图)依赖于同一状态。 来自不同视图的行为需要变更同一状态。 全局单例模式管理,把组件的共享状态抽取出来 不管在组件树的哪个位置,任何组件都能获取状态或者触发行为! 实践出真知: 1.state存放在index.js中,创建的Store实例getter,mutations,actions等,可以分离出来 2.getters存放在getter.js中,数据流为state→getter→组件,getter相当于一个数据获取过滤器,从仓库拿特定数据到组件,相当于对computed的集中处理。 3.mutations存放在mutations.js中,数据流为组件→mutations→state,mutations相当于一个数据提交发射器,从组件提交数据到仓库 4.actions存放在actions.js中,数据流为组件→actions→mutations→state,异步操作的主要场所。 5.modules是开发大型应用时需要用到的,每个module都有单独的states,getters,actions以及mutation,有一股nodejs模块的味道。 vuex三原则: 1.唯一数据源 2.保持状态只读 3.数据改变只能通过纯函数完成 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 一定要注意mutation和action的区别! mutation只变更本地的状态,也就是说,直接只去修改store中的数据。 action包含异步操作,直接调用api,通过api的数据,再提交mutation。 可以说,action只比mutation多了一个异步调用api的操作,因为调用api后,一般有2种返回结果,succes或者error,或者是promise的多种状态,根据不同的。 最近在学习redux,组件dispatch一个action到store,相当于发送一个http请求,然后store做出响应,返回一个response给组件。和vuex大致类似,唯一有区别的是,vuex还需要引入react-redux,引入Provider和connect连接组件和store。 6.统计字符串中单词出现次数 " hi how are you i am fine thank you youtube am am ",统计"you"出现的次数。 [pre] function wordCount(str,word){ var str = str || ""; var word = word || ""; var strArr = str.split(" "); var count = 0; for(var i=0;i<strArr.length;i++){ if(word===strArr[i]){ count++ } } return count; } wordCount("hi how are you i am fine thank you youtube am am","you"); [/pre] 如果字符串没有空格怎么办? [pre] function wordCount(str,word){ var str = str || ""; var word = word || ""; var count = 0; var index = str.indexOf(word); while(index!==-1){ count++; str = str.substr(index+word.length); index = str.indexOf(word) } return count; } wordCount("hihowareyouiamfinethankyouyoutubeamam","you"); [/pre] 如果不用js内置字符串函数,自己用每个字符对比呢? [pre] function wordCount(str,word){ var num = 0; var str = str+" " || ""; var word = word || ""; var strArr = str.split(""); var wordArr = word.split(""); var count = 0; function compare(arr1,a,arr2,b){ if(b+a<arr2.length){ if(arr1[a]===arr2[b+a]){ num++; return compare(arr1,a+1,arr2,b+1) } if(num===arr1.length){ count++ num = 0; } } } for(var i=0;i<strArr.length;i++){ for(var j=0;j<wordArr.length;j++){ if(wordArr[wordArr.length-1]===strArr[i+wordArr.length-1]){ compare(wordArr,0,strArr,i+0) } } } return count; } wordCount("hihowareyouiamfinethankyouyoutubeamam","am"); [/pre] 可以更加高效一些吗? [pre] function wordCount (str,word) { var str = str+" " || ""; var word = word || ""; var strArr = str.split(""); var wordArr = word.split(""); var wordArrLen = wordArr.length; var count = 0; var num = 0; function compare (arr1,a,arr2,b) { if(b+a<arr2.length){ if(arr1[a]===arr2[b+a]){ num++; return compare(arr1,a+1,arr2,b+1) } if(num===arr1.length){ count++; num = 0; } } } var j = 0; while(j<wordArrLen){ var i = 0; while(i<strArr.length){ if(wordArr[wordArrLen -1]===strArr[i+wordArrLen -1]){ compare(wordArr,0,strArr,i+0); } i++; } j++; } return count; } wordCount("hihowareyouiamfinethankyouyoutubeamam","a"); [/pre] //1.调整最高层级遍历数组,从37的2次方降到3的2次方,从1369降到9 //2.合并控制变量和控制条件,使用while替代for,去除JS引擎查询i,j是否存在的消耗,会稍微降低代码可读性 //3.对重复引用的wordArr.length,赋值给局部变量wordArrLen,在这里,Array.prototype.length的查询次数从3次降低到1次 offer - 2 莫愁前路无知己,天下谁人不识君 我本来就不想再继续找了,但是今天朋友给我一个建议,跳槽前多投几家,说不定可以找到更好的,然后我就想到 V 站上看到好多求职贴,所以就索性再试试看有没有更好的坑。 简历: 前端开发-简书-趁你还年轻233 简书 : 趁你还年轻233 github: FrankKai segmentFault:趁你还年轻 坐标:杭州 然后我在V站上发了贴,很有缘分,一个V友加了我的好友,约好时间进行了一次电话面试,后面了解好公司技术氛围和商议好薪资待遇后,成功拿到了offer,薪资待遇和第一家差不多,这家是浙大系的公司。 我和面试官很聊得来,最后甚至都聊到了吃鸡,由于电话面试太愉快了,所以这次电话面试到现在我仅记得一道跨域题了。第三家公司面试官也很赞,天气寒冷,友情提醒我注意路上冰冻,不过由于第三家面试在明天晚上才进行,所以我把第三家的BOSS直聘题也总结在一起了:一些特别棒的面试题[1] 贴一下面试题和我的回答。 1.平时有遇到过跨域问题吗? 2.下面这段代码最终输出什么? 3.["1","2","3"].map(parseInt)返回的是什么? 4.下面代码中“入库”的颜色是? 1.平时有遇到过跨域问题吗? 说到跨域问题,就一定要说到同源,什么是同源,相同协议,相同域名,相同端口,即为同源。 不同源之间的通信就会有跨域问题,一般来说是客户端访问服务器,服务器上去配置跨域。 我遇到的跨域问题都是后端去配置一下就可以解决的,比如我前端在用vue的官方推荐异步请求库axios,去请求后端的koa开启的后端服务时,就会遇到跨域的问题,例如koa使用依赖koa-cors就可以,具体的话,就是Access-Control-Allow-Origin: 源名,可以为*或者是特殊的源。或者是传统的maven或者nginx上,也可以很方便的配置跨域。 JSONP有用过吗?JSONP没用过,但是原理貌似是通过js加载一个script DOM标签进来,然后在新的script的src中引入想要执行的代码。 其实跨域问题在后端中也有类似的,只不过是叫做进程间通信,有IPC,RPC等等方式进行进程间通信。 2.下面这段代码最终输出什么? [pre] let O = function(name){  this.name = name || 'world'; }; O.prototype.hello = function(){  return function(){   console.log('hello ' + this.name);  }; }; let o = new O; let hello = o.hello(); hello(); [/pre] 年轻的我的答案是:hello world。 答案显然是不对的,因为这是一道陷阱题,陷阱就在于O.prototype.hello调用后,return的是一个函数,这么做的话,在执行新实例o的hello方法是,this其实已经变成了window。 那么答案是hello undefined吗? 年轻的你又错了,并不是。 而是 hello 。 请注意,是hello ,而不是hello undefined,而是空字符串 原因就在于window.name是事先是有定义的,而且其值为空。 不信的话你可以在控制台打印window.name,返回的是"",你再打印window.yourname试试看,比如window.frank,返回的就是undefined了。 感谢@ygh1的提醒,打印结果和运行环境也是有关的,因为node中全局是global,browser中全局是window。 刚在node里跑了下有陷阱的题目,打印出来确实是hello undefined,因为node中的global对象没有初始的name属性。 所以最正确的答案应该是: node环境:hello undefined browser环境:hello _____(非零宽空字符) 而我在工作中,遇到的更多的常见的像上面一样的工厂函数式的写法是这样的。 [pre] let O = function(name){  this.name = name || 'world'; }; O.prototype.hello = function(){   console.log('hello ' + this.name); }; let o = new O("frank"); let hello = o.hello("frank"); [/pre] 打印结果为:hello frank。 如果不传入frank的话,打印出的是默认值hello world。 3.["1","2","3"].map(parseInt)返回的是什么? A. [1,2,3] B.["1","2","3"] C.[1,1,1] D.其他 这特么又是一道陷阱题,还好我之前在看MDN的map函数时,看到过这个陷阱。 正确答案是D:其他。 其他的具体值为多少?[1,NaN,NaN]。 不敢相信吧,为什么不是可爱的[1,2,3]呢? 因为map的callback有3个参数,currentValue,index和array,parseInt有2个参数,string和radix(进制),只传入parseInt到map中的话,会自动忽略第三个参数array,但是不会忽略index,所以就会把0,1,2作为第二个参数传给parseInt。 如果还不明白的话,我们把["1","2","3"].map(parseInt)的每一步都拆开来。 parseInt("1",0) 此时将字符"1"转换为O进制数,由于0进制数不存在,所以返回Number类型的1。 parseInt("2",1) 此时将字符"2"转换为1进制数,由于超出进制数1,所以返回NaN。 parseInt("3",2) 此时将字符"3"转换为2进制数,由于超出进制数2,所以返回NaN。 至此,真相大白。 那么常用的非陷阱式map写法是怎样的呢? 像这样:["1","2","3"].map(x=>parseInt(x)) 传一个完整的函数进去,有形参,有callback,这样就不会造成因为参数传入错误而造成结果错误了,最后返回一个漂漂亮亮的经由纯函数处理后的新数组回来。 其实这里如果再深入的话,可以再考察纯函数是什么? 纯函数其实就是一个不改变输入,但是可以借助输入,产生一个以输入为原材料,经过加工处理后,输出一个全新的输出的函数,关键在于不改变输入,纯函数是编写redux的reducer必须具备的技能点。 刚才公司的大牛过来,说他从来不用parseInt,他用加号,+"1" 返回1,+"2"返回2。大牛果然大牛,黑科技是真的多。 4.下面代码中“入库”的颜色是? [pre] <ul class="list" id="list">  <li class="favorite">   <span>出库</span>  </li>  <li class="favorite">   <span class="highlight">入库</span>  </li> </ul> <style> #list .favorite:not(#list) .highlight{  color: red; } #list .highlight:nth-of-type(1):nth-last-of-type(1){  color: blue; } </style> [/pre] A. red B.blue C.black 我的答案是:我猜一下,可能是A,因为A的权重是最大的,伪类选择器的权值应该比较小吧。 面试官发来一个赞,明天可以来公司面谈吗?已经约好明天面试。 这道题的解答到此为止,因为我是真的真的对CSS不感兴趣,各位看官老爷请原谅我。 面试-3 (周一给结果) 故事还在发生着 1.说下下面两种font-size单位的异同? em rem 二者的为了保证用户修改字体大小时,保持垂直方向上的字体大小一致。与px不同,二者都是字体计算大小单位,也就是说,需要通过计算得出其大小,转换成px,微信小程序的rpx也是这样,最后还是转换成了px,可能是借鉴了rem的思想吧。 但是em相对于继承来的父元素,rem相对于根元素。听大牛说,rem在国内使用比较多,可能侍使用习惯问题。我自己也觉得rem使用起来更简单,为根元素的font-size赋一个初始值,再配合css的媒体查询,可以动态的改变这个全局的单位,可以说是牵一发而动全身,使用起来非常方便,而em的可阅读性就很差了,有的时候为了算字体大小,需要一级一级找上去,非常不直观。 现代的常用的浏览器,1rem等于16px,但是可以通过html{font-size:percentage/num }来控制。 举2个 rem和em例子对比下。 html简写结构: <html> <body> <div></div> </body> </html> rem 例子: html { font-size:62.5%; } /* =10px */ body { font-size: 2.0rem; } /* =20px */ div { font-size: 1.0rem; } /* =10px */ em 例子: html { font-size:62.5%; } /* =10px */ body { font-size: 2.0em; } /* =20px */ div { font-size: 1.0em; } /* =20px */ MDN的font-size章节给出了em和rem的非常好的解释,英文原版非常直观,我这里再贴一下: em Represents the calculated font-size of the element. If used on the font-size property itself, it represents the inherited font-size of the element. rem Represents the font-size of the root element (typically ). When used within the root element font-size, it represents its initial value (a common browser default is 16px, but user-defined preferences may modify this). 其实em和rem与MVVM框架的组件间通信有些类似,都有逐级继承和全局影响的概念。em是逐级传递的,也就是继承,框架中用props和事件订阅发布的方式也是这样,爷,父,孙的传递都是要一级一级去传递的,爷爷想直接传授点技能给孙子必须先传授给父亲,爷爷→父亲→孙子;而rem就和框架中的状态管理库很像,例如vuex和redux,抽取出一个全局的状态树,不用一级一级的去很复杂的去继承,爷爷想教教孙子,直接就可以传授给孙子,爷爷→孙子。 这次年后换工作我希望自己能在一家公司呆至少2年以上,所以这些方面我都需要了解清楚再做决定,需要慎重一些,希望能够加入一个有趣并且有实力的团队~ 作者:趁你还年轻233 链接:https://juejin.im/post/5a718cd36fb9a01caa20d8a8

评论