我们在长期的面试过程中,经历了种种苦不堪言,不诉苦感觉不过瘾(我尽量控制),然后主要聊聊常见 JavaScript
面试题的解法,以及面试注意事项
面试第一苦,面试官的土 - 有些面试官自己就非常不专业,词不达意、不知所云,这类面试常常表现为网上搜题,面试时照本宣科,只会比较候选人的答案最终的结果,自动忽略候选人对题目的见解以及解题思路。碰到这种面试官,你只有是个题霸,再加上眼缘够才能顺利入围。苦!
面试第二苦,候选人的虎 - 有些候选人的表现好像菜市场的大妈,表述永远走“朴实无华”路线,譬如:说个 iife
,他一定要说“就是那个小括号包裹,里面写一个函数,最后再来一对括号,函数里面写一堆闭包的写法”, 注意,这里面一定会说“里面写闭包”这句话,以彰显专业性,殊不知从一开始就露怯了。苦!
面试第三苦,双方都在赌 - 面试官自说自话,候选人答非所问,场面一片欢愉,结果不言而喻!
面试,是一个非常需要认真思考的事情,无论面试官还是候选人,选什么人、怎么选;面什么公司、什么岗位、题目不会该如何作答?不思考胡说一通,那不苦才有鬼!
通常面试都是聊天、笔试,机试环节常常短缺。但扪心自问,我们真的需要程序员熟背所有 Javascript
API么?我们真的需要程序员熟背所有 Web
API么?我们真的需要程序员瞬间给出“某某网站慢的完全优化方案”么?
我在想,前两问完全可以 Google
,考察的重点不在于候选人能默写多少API,而在于当他记不住的时候,是否知道去哪里查!关于后一问,瞬间给出完全优化方案?别闹了,你自己面对这个产品那么些年也没搞定,别人在没有任何了解的情况下,看一眼(有时连看都没看着,仅听描述)就能给出方案,那你们公司养的都是废物吧?这里考察的重点应该是思路,候选人会从哪几个方面思考问题的症结所在,至于是否能给出解决方案,我觉得可能性不大,一个产品涉及的东西太多,离开背景细节,给出解决方案的可能性不大。
缺少上机测试会有什么问题?(其实没什么大差错),不过可能会损失一些“嘴笨”的实践派干活能手,他们信奉“能动手就别BB”,这类人通常在与面试官 face 2 face
的“聊天”中不能讨得面试官欢心,但他们真心能写的一手好码,可惜没机会展示,也是苦!
于是我觉得大家应该互相给对方一个机会,谈谈我自己用的一套机试题目 fe-interview ,以及作为面试官希望了解的候选人关于 Javascript
方面的能力的看法。
以下是该机试题目的开始界面(retro风格),通过键盘光标上/下键选择题目,按回车键确认选中
虽然写了“算法考核”,但其实没什么算法题,这也算是标题党吧^^
确认选中后,会出现如下选择器,候选人可以再次上/下键选择”查看题目描述”、“查看测试用例”、“检验答题结果”
“查看测试用例”的目的是帮助候选人,通过查看测试用例的代码来改进自己的题目解答。
该测试工具需要本地安装,详情请看: 安装/使用手册
简单了解测试工具之后,我们就可以开始愉快的答题了
最后再来回顾一下机试目标:机试的目的不是为了让候选人在指定时间内完成一个完美无瑕的功能,那是 高考 !我们期待的是通过不同的题目,考察候选人的基本功、编码能力、思考方法。。。,通过这些综合指标,判断该候选人是否一个你期望的 JavaScript
工程师
下面让我们一起走上答题之路
题目非常简单,完成一个可以删除数组指定下标对应元素的函数
答案:
var removeArray = function(arr, index) { arr.splice(index, 1); }; module.exports = removeArray;
真心没什么花哨的地方,这个题目不涉及任何高深的知识。旨在考察候选人对基础API的熟练程度,关于 splice
你可能想知道 更多
答案:
var isString = function(value) { return Object.prototype.toString.call(value) === '[object String]'; }; module.exports = isString;
这里考察的重点是 new String('hello')
到底是个什么东西?至于我给的答案用了 Object.prototype.toString
方法,我可以负责任的告诉你,这个做法是有风险的,因为该方法的实现由平台提供,意思是:不同的 JavaScript
执行引擎实现可能不同,所以结果并不能保证。还是那句话,结果不是最重要的,解题思路,看清本质最为关键,也是最重要的能力。当然关于 Object.prototype.toString
,你也可能想知道 更多
这题可能对一些同学来讲有点过了,但相信我,我的测试用例并不变态,你完全不必写出一个“完美”的柯里化。只要按照我题目的思路,甚至打出来测试用例看看,就能实现这个题目了。
首先为了普法,还是先简单介绍下什么是“柯里化”,说白了柯里化也是一个函数。假设我们有函数 A
经过柯里化函数处理过后, A
就被赋予了一种能够被部分执行的能力!这话说的听不懂了是吧?我们来看个例子:
//假设我们有个add函数,她接受两个参数a和b;并返回二者之和 var add = function(a, b){ return a + b; };
简单吧?那么问题来了,如果我现在只给了你一个参数a,另一个参数我想过会儿再给你,怎么办?传统思维是那就过一会再调用 add
呗!这当然没错!但是凭借柯里化,我们有另外一种思路使得以下成为可能:
var add3 = add(3);//执行了一半 setTimeout(function(){ console.log(add3(5));//这里拿到了另一半参数5,再计算最终结果 }, 5000);
我这里不会大篇幅讲柯里化有多牛逼的好处,当然如果你有兴趣,可以看 Why Curry Helps
答案:
var currying = function(func) { var len = func.length;//获取一个函数形參的个数 var getCurry = function(params) { return function() { //参数拼接 var next = params.concat(Array.prototype.slice.call(arguments)); //持续接收的参数已经满足当初原始函数的形參个数,执行原始函数,返回结果 if (len - next.length <= 0) { return func.apply(this, next); } //不满足个数,将已经获取的参数继续递归 return getCurry(next); }; }; return getCurry([]); }; module.exports = currying;
如此简单的题目,我想考察的依旧是 Array
的内置API,不记得不要紧,我是允许上网查的(谁没有个记不住的时候)。但你要是玩出花来,譬如有人写了几十行的方法,这个我就有点懵了。
答案:
var duplicate = function(array) { return array.concat(array); }; module.exports = duplicate;
其实 concat
足矣。关于 concat
, 更多详情
“浅拷贝”,顾名思义,对于引用类型的数据,只拷贝其引用,也就是题目中 copied[0].name = 'world';
之后,原先的 value
也被改了。
答案:
var shallowCopy = function(value) { return Array.prototype.slice.apply(value); }; module.exports = shallowCopy;
没有标准答案, for loop
照样能完成本题。关键在于候选人是否理解了 浅拷贝
这个词
如何“拍平”一个多维数组,这是好玩意儿
答案:
var flatten = function(array) { return array.reduce(function(previous, i) { if (Object.prototype.toString.call(i) !== '[object Array]') { return (previous.push(i), previous); } return (Array.prototype.push.apply(previous, flatten(i)), previous); }, []); }; module.exports = flatten;
本题在“递归”这个问题上,做了一些考虑。我想考查的主要是面对多维数组,候选人将如何处理!
我的答案用了 reduce
方法,如果你还没用过,你需要看 reduce 。另外可能有人对于逗号的使用有疑惑,可以看 Comma_Operator 。另:我的答案绝不敢称最佳,随时欢迎优化/修正。
考查 ES5
时代基于 prototype
的类继承实现,如果你心系 ES6
,这个可以忽略。 但了解总归是好的 。
答案:
var Parent = function(name) { this.name = name; }; Parent.prototype.getName = function() { return this.name; }; module.exports = Parent;
var Parent = require('./Parent'); var Son = function(parentName, name) { Parent.call(this, parentName); this.childName = name; }; Son.prototype = new Parent(); Son.prototype.constructor = Son; Son.prototype.getChildName = function() { return this.childName; }; module.exports = Son;
以上算是基本继承概念,如果不清楚的,看看这篇教程 OOP in JS, Part 2 : Inheritance
通过长期观察我们发现一个现象,就是在 ES2015
甚至 ES2016
大行其道的今天,仍然有人打着“我要兼容IE8”的旗号拒绝进步。自 ES5
开始就有的 Array
新方法 map
, forEach
、 reduce
常常有人搞不懂,也不会用,更不知道干嘛用的。往往一言不和就吐一堆 for
循环出来恶心人!本题主要就是甄别候选人是否自己口中所说的那样“积极、爱学习”,如果连 ES5
就有的常见方法都不会,还是“积极、爱学习”,那我只能呵呵了!
答案:
var map = function(arr, func, ctx) { var array = []; arr.forEach(function(i, index) { array.push(func.call(ctx, i, index, arr)); }); return array; }; module.exports = map;
这里解法也是多种多样,无所谓用哪一种,关键在于是否真正理解了什么是 map
?文档看: map
最为 ES5
里 Array
的几个好兄弟, reduce
的用法/原理我想还是应该掌握的。
答案:
var reduce = function(arr, func, initialValue) { var base = typeof initialValue === 'undefined' ? arr[0] : initialValue; var stepForward = typeof initialValue === 'undefined' ? 1 : 0; var startPoint = stepForward; arr .slice(startPoint) .forEach(function(val, index) { base = func(base, val, index + stepForward, arr); }); return base; }; module.exports = reduce;
仍然不理解 reduce
工作原理的,看 Understanding Eloquent Javascript’s Reduce function
永远获取传入参数的最终执行结果,如果是函数的,就执行、执行、再执行,直到拿到了最终的非函数结果。
答案:
var value = function(anything) { if (Object.prototype.toString.call(anything) !== '[object Function]') { return anything; } return value(anything()); }; module.exports = value;
今天实在写不动了,先来10题试试反响,如果大家觉得还有用,我再继续补后面的题目。当然也欢迎直接 Github
给我PR, star
那自然是最好的啦^^