阅读 12277

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

最近看到有人分享16届前端进阿里(前端学习群的群主),17届前端杭州收割offer(优秀的路人)。 刚好同为17届的我最近也很轻松拿了2个offer,但是我看到他们写的题目回答并不是很详细,所以强迫症的我也想来和大家分享下,我会把所有没挂掉的和挂掉的都分享出来,遇到的每一道题目都做详细的分析,所以文章有点长哦。

长文慎入哈,但是有酒有故事,小板凳我都给你搬好啦,快坐下!

故事开始咯~

本来想等过完年回来再换工作的,但是前段时间有猎头在简书私信我,说看我的文章写得还行,问我想不想换工作。 然后我就更新了简历,发过去了,邀请我面试,但是这家公司在北京 /西安,后来就没去,但是故事就此开始了,这反而促使我走上了换工作的道路。

贴一下自认为写得不错的几篇文章:

CSS3动画卡顿性能优化解决方案

深度剖析0.1 +0.2===0.30000000000000004的原因

如何解决0.1 +0.2===0.30000000000000004类问题

记一次简单的DOM XSS攻击实验

你真的理解==和===的区别吗?

经典CSS坑:如何完美实现垂直水平居中?

自己从大三暑假实习到现在工作半年多,接近快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日

于冻成狗的冰雪杭州

关注下面的标签,发现更多相似文章
评论

查看更多 >