最近看到有人分享16届前端进阿里(前端学习群的群主),17届前端杭州收割offer(优秀的路人)。 刚好同为17届的我最近也很轻松拿了2个offer,但是我看到他们写的题目回答并不是很详细,所以强迫症的我也想来和大家分享下,我会把所有没挂掉的和挂掉的都分享出来,遇到的每一道题目都做详细的分析,所以文章有点长哦。
长文慎入哈,但是有酒有故事,小板凳我都给你搬好啦,快坐下!
故事开始咯~
本来想等过完年回来再换工作的,但是前段时间有猎头在简书私信我,说看我的文章写得还行,问我想不想换工作。 然后我就更新了简历,发过去了,邀请我面试,但是这家公司在北京 /西安,后来就没去,但是故事就此开始了,这反而促使我走上了换工作的道路。
贴一下自认为写得不错的几篇文章:
深度剖析0.1 +0.2===0.30000000000000004的原因
如何解决0.1 +0.2===0.30000000000000004类问题
自己从大三暑假实习到现在工作半年多,接近快1.5+年的时间,一门心思扎到前端开发领域,高强度式的工作和学习,买书啃书,写代码看代码,写博客看博客,提问题回答问题,投入了几乎是大一到大三学习时间总和的时间学习前端开发。
最近阴差阳错开始笔试,面试,虽然都没怎么准备,但是很轻松就收获了一家阿里子公司,一家浙大系公司的offer,再加上昨天被第三家面试官的赞赏,之前的妄自菲薄一下烟消云散。
由于自己这次换工作,希望能够在一家公司呆至少2年以上,非常希望能和一帮对前端技术有见解有思考有追求的小伙伴们,一起去探索钻研有趣的前端世界。所以我决定尝试更多的面试,加入到一个更适合自己的团队。
同时也希望通过这篇面试分享,能够给和我一样努力的前端小伙伴们一些鼓励,也希望能够把最真实的自己展现给大家。
offer - 1 天道酬勤,付出终有回报
拒绝了第一家主动找到我的公司后,又有家阿里子创业公司在 BOSS 直聘里私信我,然后抱着测试水平的心态面了。 现场笔试不怎么样,但是他们看到我简书文章还算可以,就让我重做了一遍题目: 一些特别棒的面试题[0], 最后一道单词与字符串匹配的题的拓展,都是面试官一步步引导我去深入的,感觉学习到很多。
没想到结果是比较让人惊喜的,前辈说我的学习能力比较强,所以愿意给我这个机会,给的薪资比现在高 2K,关键是听说有许多项目可以做,据说面试我的老板以前也是在阿里的技术专家。
贴一下面试题和我的回答。
1.说一下你熟悉的设计模式
2.说一下你理解的模块机制
3.MVVM原理
4.最熟悉的框架路由机制
5.状态管理
6.统计字符串中单词出现次数
复制代码
1.说一下你熟悉的设计模式
我最熟悉的设计模式:工厂模式(ES5),组件设计模式(ES6) 工厂模式(ES5,基于prototype。此例中基类Base,子类Factory)
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();
}
})
复制代码
组件设计模式(ES6,基于class,方便继承和初始化,也是React组件的推荐写法,我比较喜欢。此例中父类Compnent,子类Retrive)
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>
);
}
}
复制代码
2.说一下你理解的模块机制
AMD: 异步模块加载规范。 a.js,定义一个依赖jQuery和echrts的组件。
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()
});
复制代码
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"出现的次数。
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");
复制代码
如果字符串没有空格怎么办?
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");
复制代码
如果不用js内置字符串函数,自己用每个字符对比呢?
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");
复制代码
可以更加高效一些吗?
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");
//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.下面这段代码最终输出什么?
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();
复制代码
年轻的我的答案是: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 _____(非零宽空字符)
复制代码
而我在工作中,遇到的更多的常见的像上面一样的工厂函数式的写法是这样的。
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");
复制代码
打印结果为: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.下面代码中“入库”的颜色是?
<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>
复制代码
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,抽取出一个全局的状态树,不用一级一级的去很复杂的去继承,爷爷想教教孙子,直接就可以传授给孙子,爷爷→孙子。
offer-4
故事还在发生着
offer-5
故事还在发生着
我想成为一个很厉害的人,让这个世界因为有了我而有一点点不一样,哪怕,只有一点点
贴一下自己理想中的团队的关注点
1.个人前端技术的成长空间
比如资深前端对初级前端的指导,前端团队成员间的学习和提升等等 这个最重要,我的目标是2年时间成为基础扎实,有独立前端技术见解的开发者
2.公司对员工成长和进步的重视程度
例如新人培训,技术分享,技术翻译等等
3.公司加班情况是996还是10105或者是按照项目进度安排的
只要是一帮追求前端极致的人,996是完全OK的
4.员工补贴,福利
例如过节补贴,工会福利
5.绩效考核和奖金
例如绩效考核,年终奖金
这次年后换工作我希望自己能在一家公司呆至少2年以上,所以这些方面我都需要了解清楚再做决定,需要慎重一些,希望能够加入一个有趣并且有实力的团队~
简历戳这里: 前端开发-简书-趁你还年轻233
最后再说句体题外话,3家公司都在用vue,都问我有没有用过vue...
That's it !
趁你还年轻
2018年1月31日
于冻成狗的冰雪杭州