我在Twitter和Stripe这两家公司工作期间会负责一些前端开发者的面试。在面试过程中我们有很大的决定权,这里和大家一起分享一些我设计的不同类型的面试问题。
首先,我要警告各位的就是: 招人很难!尤其是要在45分钟之内判断一个人是否适合岗位更是一项高难度的工作。 面试存在的问题就是大家都想招到像一个像自己一样的人。 每个通过我面试的人的思维方式可能都和我比较相似, 但这样肯定是不对的。 正因如此, 其实目前为止我做的每一个决定都有一部分运气成分。 但是我想这种方式会是个很好的开始。
理想情况下,应聘者应该有一份比较完整的GitHub‘简历’, 这样我们可以一起来回顾他们参与的开源项目。 通常我会先浏览他们的代码,然后针对某一个具体的代码设计问他们一些问题。 如果应聘者在这一部分表现非常优秀,就可以直接进入团队社交能力的考察部分。否则的话我会让他们做一些编程题目。
我面试的时候是非常注重实践的, 整个面试过程几乎全都是在写代码。 我不会问一些比较抽象的或者算法相关的问题。其他的面试官如果愿意的话可以考察这些方面, 但我觉得这些知识未必是一个前端开发者所必需的。我问的问题看起来比较简单,但实际上每一类问题都可以让我洞悉应聘者在JavaScript的某一方面的知识。
应聘者可以使用自己的笔记本电脑也可以用我的,但在我这里是不会用白板的。他们也可以使用任何适合自己的编辑器,但我通常直接用Chrome的控制台来检查的应聘者的程序输出结果。
第一部分:对象原型
我们先从简单的来。实现一个spacify函数:接受一个字符串作为参数,然后把这个字符串的每个字符都用空格隔开后返回。例如:
1 |
spacify('hello world') // => 'h e l l o w o r l d' |
虽然这个问题看起来非常容易,但结果却证明从这个问题问起是很合适的,尤其是对于一些电话面试者,他们声称了解JavaScript,却连一个完整的函数都不会写。下面是这个题目的正确答案,有的应聘者通过循环来实现也是可以的。
1 2 3 |
function spacify(str) { return str.split('').join(' '); } |
接下来这个问题是让应聘者直接为String对象增加spacify的函数,像这样:
1 |
'hello world'.spacify(); |
通过这个问题我可以了解到应聘者对于函数原型基础知识的掌握情况。另外这个问题经常会引发另外一个有趣的讨论:直接在prototypes上尤其是在Object的prototypes上定义属性的风险。
最终的答案类似下面的代码:
1 2 3 |
String.prototype.spacify = function(){ return this.split('').join(' '); }; |
这时候我还会让应聘者解释函数表达式(expression)和函数声明(declaration)的区别。
第二部分:参数
接下来我会问一些简单的问题,这些问题可以帮我了解到应聘者对参数对象的理解程度。
首先,调用一个尚未定义的log函数:
1 |
log('hello world') |
然后我让应聘者去实现log函数:接受一个string参数然后直接传给console.log(),正确答案就在下面,但有些比较优秀的应聘者会直接使用apply函数来实现。
1 2 3 |
function log(msg){ console.log(msg); } |
完成上一步后我会修改调用log的方式:传递多个参数。告诉应聘者我希望log函数不止接收一个数字作为参数,它应该可以接受任意个数字作为参数。同时我也提醒他们cosole.log()本身就可以接收多个参数。
1 |
log('hello', 'world'); |
理想情况下应聘者应当直接使用apply来实现这个功能。但有时他们会混淆apply和call的二者的区别,这时你可以给他们一些提示。另外将console作为上下文参数这一点也很重要。
1 2 3 |
function log(){ console.log.apply(console, arguments); }; |
然后我会让要求在每一条日志消息前加上“(app)”的前缀,例如:
1 |
'(app) hello world' |
现在,问题就有点棘手了。能力强些的应聘者应当知道arguments是一个伪数组,在使用它之前得先把它转换成标准数组。通常我们用Array.prototype.slice就可以实现这一点,像下面这样:
1 2 3 4 5 |
function log(){ var args = Array.prototype.slice.call(arguments); args.unshift('(app)'); console.log.apply(console, args); }; |
第三部分:上下文
下面的这一组面试题可以考察应聘者对于JavaScript中context和this的理解。我先给出下面的定义,注意,count的属性是从当前的上下文中读取的。
1 2 3 4 5 6 |
var User = { count: 1, getCount: function() { return this.count; } }; |
然后我会让应聘者写出下面代码的输出结果:
1 2 3 |
console.log(User.getCount()); var func = User.getCount; console.log(func()); |
这个题目正确的答案是1和undefined。令人吃惊的是有很多人会在这种关于上下文的基础知识上犯错。func函数被调用时,它的上下文是windows,而windows是没有count属性的。我把这些都和应聘者做了解释,然后我问他如何才能保证func函数始终都能以User作为上下文被调用,这样它就能正确运行从而返回1。
正确的答案是使用Function.prototype.bind,例如:
1 2 |
var func = User.getCount.bind(User); console.log(func()); |
通常我会告诉应聘者有一些老的浏览器是不支持bind函数的,然后让他们自己来写一个函数来模拟。有一些基础差的应聘者并不认可这一点,但对我来讲每一个被雇佣的应聘者对apply和call都应该有比较深入的理解,这一点很重要!
1 2 3 4 5 6 |
Function.prototype.bind = Function.prototype.bind || function(context){ var self = this; return function(){ return self.apply(context, arguments); }; } |
如果应聘者像上面那样实现了bind并且还判断了当前浏览器是否已经支持bind函数,那么应聘者可以得到额外的加分。
此时,如果应聘表现的很出色,我会让他们去实现currying参数。
第四部分:Overlay库
面试的最后这一部分里,我会让应聘者做一些更加实际的事情,通常是去实现一个‘overlay’的库。这很方式很管用,它涉及到了整个前端开发所用到的技术:HTML、CSS 和JavaScript。如果应聘者在前面几个环节表现优秀,我会尽早的开始这一部分的问题。
具体的实现因人而异,但是这里几个关键点需要注意!
对于overlay covers,最好使用 position: fixed 而不是 position: absolute,这样即使窗口滚动的时候也可以保证层铺满整个窗口。如果应聘者没有注意到这一点我会提示他们,然后问他们这两者的区别。
1 2 3 4 5 6 7 8 |
.overlay { position: fixed; left: 0; right: 0; bottom: 0; top: 0; background: rgba(0,0,0,.8); } |
从把内容放置到层的中心位置的方式也可以为面试官提供一些信息。有些应聘者可能会使用CSS和绝对位置,但这样的前提是内容必须是固定宽度和长度的。另外的应聘者也可能会选择用JavaScript来定位。
1 2 3 4 5 6 7 8 |
.overlay article { position: absolute; left: 50%; top: 50%; margin: -200px 0 0 -200px; width: 400px; height: 400px; } |
我还会要求他们实现单击关闭层的功能,然后就可以顺势讨论下几种不同类型的事件传播机制。大多数应聘者会直接为层设置一个事件监听器。
1 |
$('.overlay').click(closeOverlay); |
看着是对的,但很快你就会发现在这个层的子元素上单击也会关闭层,这明显不是我们预期的效果。解决方法是先检查事件的targets来确保不是一个传播事件,像这样:
1 2 3 4 |
$('.overlay').click(function(e){ if (e.target == e.currentTarget) closeOverlay(); }); |
其它
其实这些问题只覆盖了前端知识的很小一部分,面试的时候你可以问很多其他方面的问题,例如性能、HTML5 APIs, AMD vs CommonJS modules、 构造函数、数据类型以及盒模型等。我经常会根据应聘者的兴趣来搭配不同类型的问题。
此外我推荐大家到前端开发者面试题集锦和JavaScript花园去寻找一些灵感!