20

我们在长期的面试过程中,经历了种种苦不堪言,不诉苦感觉不过瘾(我尽量控制),然后主要聊聊常见JavaScript面试题的解法,以及面试注意事项

忆苦

面试第一苦,面试官的土 - 有些面试官自己就非常不专业,词不达意、不知所云,这类面试常常表现为网上搜题,面试时照本宣科,只会比较候选人的答案最终的结果,自动忽略候选人对题目的见解以及解题思路。碰到这种面试官,你只有是个题霸,再加上眼缘够才能顺利入围。苦!

面试第二苦,候选人的虎 - 有些候选人的表现好像菜市场的大妈,表述永远走“朴实无华”路线,譬如:说个iife,他一定要说“就是那个小括号包裹,里面写一个函数,最后再来一对括号,函数里面写一堆闭包的写法”, 注意,这里面一定会说“里面写闭包”这句话,以彰显专业性,殊不知从一开始就露怯了。苦!

面试第三苦,双方都在赌 - 面试官自说自话,候选人答非所问,场面一片欢愉,结果不言而喻!

面试,是一个非常需要认真思考的事情,无论面试官还是候选人,选什么人、怎么选;面什么公司、什么岗位、题目不会该如何作答?不思考胡说一通,那不苦才有鬼!

通常面试都是聊天、笔试,机试环节常常短缺。但扪心自问,我们真的需要程序员熟背所有Javascript API么?我们真的需要程序员熟背所有Web API么?我们真的需要程序员瞬间给出“某某网站慢的完全优化方案”么?

我在想,前两问完全可以Google,考察的重点不在于候选人能默写多少API,而在于当他记不住的时候,是否知道去哪里查!关于后一问,瞬间给出完全优化方案?别闹了,你自己面对这个产品那么些年也没搞定,别人在没有任何了解的情况下,看一眼(有时连看都没看着,仅听描述)就能给出方案,那你们公司养的都是废物吧?这里考察的重点应该是思路,候选人会从哪几个方面思考问题的症结所在,至于是否能给出解决方案,我觉得可能性不大,一个产品涉及的东西太多,离开背景细节,给出解决方案的可能性不大。

觉醒

缺少上机测试会有什么问题?(其实没什么大差错),不过可能会损失一些“嘴笨”的实践派干活能手,他们信奉“能动手就别BB”,这类人通常在与面试官face 2 face的“聊天”中不能讨得面试官欢心,但他们真心能写的一手好码,可惜没机会展示,也是苦!

于是我觉得大家应该互相给对方一个机会,谈谈我自己用的一套机试题目fe-interview,以及作为面试官希望了解的候选人关于Javascript方面的能力的看法。

以下是该机试题目的开始界面(retro风格),通过键盘光标上/下键选择题目,按回车键确认选中

虽然写了“算法考核”,但其实没什么算法题,这也算是标题党吧^^

图片描述

确认选中后,会出现如下选择器,候选人可以再次上/下键选择"查看题目描述"、“查看测试用例”、“检验答题结果”

图片描述

"查看测试用例"的目的是帮助候选人,通过查看测试用例的代码来改进自己的题目解答。

该测试工具需要本地安装,详情请看:安装/使用手册

简单了解测试工具之后,我们就可以开始愉快的答题了

最后再来回顾一下机试目标:机试的目的不是为了让候选人在指定时间内完成一个完美无瑕的功能,那是高考!我们期待的是通过不同的题目,考察候选人的基本功、编码能力、思考方法。。。,通过这些综合指标,判断该候选人是否一个你期望的JavaScript工程师

下面让我们一起走上答题之路

答题之路

01 - 请尝试删除数组的指定下标对应的元素

图片描述

题目非常简单,完成一个可以删除数组指定下标对应元素的函数

答案:

var removeArray = function(arr, index) {
    arr.splice(index, 1);
};

module.exports = removeArray;

真心没什么花哨的地方,这个题目不涉及任何高深的知识。旨在考察候选人对基础API的熟练程度,关于splice你可能想知道更多

02 - 请尝试完成可以判断传入变量是否为string的模块

图片描述

答案:

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,你也可能想知道更多

03 - 请尝试完成一个简单的使柯里化(currying)模块

图片描述

这题可能对一些同学来讲有点过了,但相信我,我的测试用例并不变态,你完全不必写出一个“完美”的柯里化。只要按照我题目的思路,甚至打出来测试用例看看,就能实现这个题目了。

首先为了普法,还是先简单介绍下什么是“柯里化”,说白了柯里化也是一个函数。假设我们有函数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;

04 - 请尝试完成一个'duplicate(重复)'的模块

图片描述

如此简单的题目,我想考察的依旧是Array的内置API,不记得不要紧,我是允许上网查的(谁没有个记不住的时候)。但你要是玩出花来,譬如有人写了几十行的方法,这个我就有点懵了。

答案:

var duplicate = function(array) {
    return array.concat(array);
};

module.exports = duplicate;

其实concat足矣。关于concat更多详情

05 - 请尝试完成可以浅拷贝的模块

图片描述

“浅拷贝”,顾名思义,对于引用类型的数据,只拷贝其引用,也就是题目中copied[0].name = 'world';之后,原先的value也被改了。

答案:

var shallowCopy = function(value) {
    return Object.assign(new (Object.getPrototypeOf(value).constructor)(), value);
};

module.exports = shallowCopy;

根据@Tommy Troy Lin的PR,对答案做出修正。原因是我在题目里提到了对Object的支持,而我的答案以及题目考核的最初想法都没有考虑Object的情况(记忆错乱了)。再次感谢指正

关于Object.assign,不明白的同学看这里assign;对于Object.getPrototypeOf不清楚的同学,看getPrototypeOf

06 - 请尝试完成一个类似'_.flatten'的模块

图片描述

如何“拍平”一个多维数组,这是好玩意儿

答案:

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。另:我的答案绝不敢称最佳,随时欢迎优化/修正。

07 - 请尝试完成一个'继承'的实现

图片描述

考查ES5时代基于prototype的类继承实现,如果你心系ES6,这个可以忽略。但了解总归是好的

答案:

Parent

var Parent = function(name) {
    this.name = name;
};

Parent.prototype.getName = function() {
    return this.name;
};

module.exports = Parent;

Son

var Parent = require('./Parent');

var Son = function(parentName, name) {
    Parent.call(this, parentName);
    this.childName = name;
};

Son.prototype = Object.create(Parent.prototype);

Son.prototype.constructor = Son;

Son.prototype.getChildName = function() {
    return this.childName;
};

module.exports = Son;

以上算是基本继承概念,如果不清楚的,看看这篇教程OOP in JS, Part 2 : Inheritance。你也可能对与Object.create的使用感到疑惑,那么请看create

接受@我仍旧在这里 建议,把new Parent()换成了Object.create(Parent.prototype)

08 - 请尝试完成一个类似'_.map'的模块

图片描述

通过长期观察我们发现一个现象,就是在ES2015甚至ES2016大行其道的今天,仍然有人打着“我要兼容IE8”的旗号拒绝进步。自ES5开始就有的Array新方法map, forEachreduce常常有人搞不懂,也不会用,更不知道干嘛用的。往往一言不和就吐一堆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

09 - 请尝试完成一个类似'_.reduce'的模块

图片描述

最为ES5Array的几个好兄弟,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

10 - 请尝试完成一个获取原始数据值的模块

图片描述

永远获取传入参数的最终执行结果,如果是函数的,就执行、执行、再执行,直到拿到了最终的非函数结果。

答案:

var value = function(anything) {
    if (Object.prototype.toString.call(anything) !== '[object Function]') {
        return anything;
    }
    return value(anything());
};

module.exports = value;

今天实在写不动了,先来10题试试反响,如果大家觉得还有用,我再继续补后面的题目。当然也欢迎直接Github给我PR,star那自然是最好的啦^^

欲知后事,请看下集


leftstick
27.3k 声望1.5k 粉丝

沙滩一卧两年半,今日浪打我翻身