Fork me on GitHub

前端面试官如何从笔试中选拔人才

    我没有参加过一线IT公司的笔试,但是我听说大公司都喜欢笔试,于是从网上搜了一些面试方面的题来试手。无论是笔试还是被笔试,有备无惧嘛。我个人倾向于javascript编程方向的开发,所以面试题自然也是选择这方面相关的。网上各类面试题的质量参差不齐,其中有一篇叫《如何面试前端工程师》的博文里边给的题型是我认为最好的。题量很少,但是考察的方向都基本上覆盖了。作者说他是个行业派(言下之意他也是写代码的),注重前端编程的能力。下面我就拿它的题来分析一下:

第一部分:Object Prototypes (对象原型)

题目是:定义一个方法,传入一个string类型的参数,然后将string的每个字符间加个空格返回,例如:

spacify('hello world') // => 'h e l l o  w o r l d'    

这题我认为是比较简单的,但是考虑到好的考题都是容易的在前,稍难的在中间,最难的在后面。这样布局是很合乎常理的。让面试者有一个热身过程,做到良好发挥。

因为做为面试官,不是要把面试者都难倒,证明你个人有多牛X,而是要选出合适的人才,为企业创造价值。好像说的有点远了,继续分析试题。

我一下子就想出好几个方法,但是保险起见,我用了这个:

function spacify(str) {
      return str.split('').join(' ');
    }

接下来,难度加大,如何把这个方法放入String对象上面,例如:

'hello world'.spacify();

显然这个问题是要考察侯选人是否对function prototypes(方法原型)有一个基本的理解。我把上面的方法简单改了一下就出来了:

String.prototype.spacify = function(){
    //能否理解这里的this 是本题的关键
      return this.split('').join(' ');
    };

我认为这题即考察了原型对象,又考察了this的经典用法,如果不是有多年的编程经验,是很难领会这一行代码的。当然,如果说你是背出来的,另当别论了。

第二部分:apply和call的用法

定义一个log函数,它可以代理console.log的方法。这个简单,再基础不过了:

function log(msg) {
      console.log(msg);
    }

我发现,越是简单的题,我越小心,总要多看几遍,身怕里边有什么坑在等着我跳,仔细看了三遍,确实是没有地雷. 

接着题目难度加大,要求可以打印多个参数,参数个数不确定,温馨提示可以使用apply。

既然提示都这么明显了,用apply是没错的:

function log(){
      console.log.apply(console, arguments);
    };

这里要注意的是,用apply的时候,第一个参数是log执行环境的this指向。第二个参数必须是数组,或者是arguments这样的类数组对象。对于这个题目来说,如果第一个参数用

this的话,指的是在window中去找log这个方法, 那不成了自己找自己啊。

接下来要给每一个log消息添加一个"(app)"的前辍,比如:

 log('hello world'); //'(app) hello world'

现在可能有点麻烦了。好的侯选人知道arugments是一个伪数组,然后会将他转化成为标准数组。通常方法是使用Array.prototype.slice,像这样:

function log(){
      var args = Array.prototype.slice.call(arguments);
      args.unshift('(app)');

      console.log.apply(console, args);
    };

当然你也可以不用call或apply之类的方法实现,但是按照出题者的意思走,可以收到事半功倍的效果. 如果是技术官,他会认为你懂他!如果不是,他会认为你的答案很贴合“标准答案”。

第三部分:上下文

关于this的身份,一直以来都是各大面试题喜欢八卦的命题。这次也不例外,题目是一段代码:

var User = {
  count: 1,

  getCount: function() {
    return this.count;
  }
};

下面几行,log输出的会是什么?

 console.log(User.getCount());

    var func = User.getCount;
    console.log(func());

对于一个javascript 编程的老手来说,这种情况下,正确的答案是1和undefined,一点都不会意外的,但是对于以前一直是从事套站写网页模版的候选人来说,就未必了。

当然,我在此并没有要黑这些资深网页制作者的意思。

那么问题来了,怎么让这个答案输出都是1呢?

正确的答案是使用Function.prototype.bind,例如:

var func = User.getCount.bind(User);
    console.log(func());

用bind?这些从ie5时代走过来的前辈们会觉得难以接受。这个方法对老版本的浏览器不起作用,好吧,那就顺道问下,这个怎么兼容这个bind好了。

聊到兼容,理论派或者说思路派就要开始滔滔不绝了,balabala...讲一大堆。他们甚至不屑于写这样的代码,认为太低估了他们的实力。

为了这篇文章的严谨和真实性,我特意拿到我所在的前端开发群里考察了一翻,注意,这个群里边有许多“XX神之类的人物”哦,当然也有我自己。

一起来看看它们的答案吧:

杭州的朋友给的答案:

这个暂且不说他写的对与错,这逻辑就有点让人眼晕,直接放弃。事后我采访他,原来是从网上copy的,这种不经过自己吸收的拿来主义,吃再多也是长不胖滴。

再看一位广州的朋友给的答案:

 Function.prototype.binds=function(obj){
        var fc=this;
        return function(){
            fc.call(obj);
        }
    }

再看一位烟台的朋友:

..... 其它的就不一一例举了。我来说说这两个答案。 
这两位同学的思路都是正确的,但是细节上都有错误。这也是我要求贴源码,不要写思路的原因。
有些人说起思路来涛涛不绝,但是写出来的代码却是漏洞百出。看起来很简单,却不一定做的很容易。同意的点个赞!
在宣布参考答案之前,我来做为一回面试官,过一把干瘾也好嘛。从答题结果来看,我会选广州的朋友。

下面说下我的理由:
虽然广州的朋友修改过多次仍然没有全对,但是都是粗心或是考虑不全面,假以时间,还是可以胜任工作。
而烟台的那位虽然写的很简洁,也非常注意命名规范,思路也明确,但是Function.apply这一行,暴露了他基础的不足,需要学习的时间成本明显要多一些。当然如果悟性很好的话,另当别论。
以上纯属娱乐,请看到的朋友不要当真。

下面看下我的参考答案:

Function.prototype.bind = function(context){
    var self = this;
    var arg  = [].slice.call(arguments,1);
 
    return function(){
        return self.apply(context,arg)
    }
}

检测一下:

setTimeout(function(msg){
    console.log(this.name,this.age,msg)
}.bind({
    name : 'frog',
    age  : 18
},'blogs'))

var User = {

  count: 1,

  getCount: function() {
    return this.count;
  }

};

var func = User.getCount.bind(User);

console.log(func());

上面的结果和使用bind的结是一致的,但是既然是做兼容,这样写,相当于忽略了那些本来就支持bind方法的浏览器了。得修改一下:

Function.prototype.bind = Function.prototype.bind || function(context){
    var self = this;
    var arg  = [].slice.call(arguments,1);
    return function(){
        return self.apply(context, arg);
    };
}

这个地方,可以顺便提问关于闭包的问题。

 

函数式声明提升及没有块级作用域

这个是我自己加的,因为我在工作中,经常会遇到这样或那样的问题,多少都与这些东东粘点边。下面是一个例子:

function say(){
    function name(){
        console.log('frog')
    }
    return name();
    function name(){
        console.log('hello')
    }
}

var name = say();

输出的是hello;我承认,不会有人傻X到写这样无聊的代码。这个题的原型我不记得了,反正大致就是说明这样一个问题。当然关于这个例子你肯定还有更好的代码。例如:

function say(){
    ok(); //这里会报错
    var ok = function(){
        console.log('ok')
    }
}

//----------------
function say(){
    ok(); //这样就正常了
    function ok(){
        console.log('ok')
    }
}

变量声明则不会被提升,函数式声明则会提升。再看一个例子:

var name = 'frog'
function hello(){
    alert(name); // undefined
    var name = 'bbc';
}

很多初学者都在这里翻船的。原因在于他们只记得没有块级作用域,但是没理解什么叫块级作用域。

在javascript中,函数是可以形成一个独立作用域的,变量的查找,首先是就近原则,先看自己有没有,自己没有,就会自动跑到外层去找,这一点和其它语言可能不一样,它会自动跑外边去找。在整个hello作用域内,只要定义了name这个变量,就不会去window中找,不过呢,在hello自己的作用域内,还有一个规则,申明之前调用,都是undefined,申明且赋值之后调用才会有值。alert(name)发生在申明之前,所以会弹出undefined就是这么个道理。

下面我再来说说这个没有块级作用域:

for(var i=0;i<10;i++){
    //...
}
alert(i)//10

这个块,指的就是两个大括号之间的区域, 在javascript中,原本是不存在这个问题的,出现这个疑问,是由那些搞过c语言之类的人转来搞javascript带来的。他们以前的知识

中,循环之后,i自动销毁了,但是javascript中不是这样的。只要记得javascript中,函数才是划分作用域的就可以了。

类似的情况还有判断:

function fn(){if(true){
    var i = 10
  }
  return i;
}

 

其他方面

当然这些问题只能覆盖前端一点点的知识的,还有很多其他的方面你有可能会问到,像性能,HTML5 API, AMD和CommonJS模块模型,构造函数(constructors),类型和盒子模型(box model)。以及热门的移动前端开发框架angula.js,spa应用,栅格布局, 异步编程问题,甚至可以问问算法,数据库什么的。只要给的起价,怎么折腾都行。

 

posted on 2015-01-22 14:50  bjtqti  阅读(4046)  评论(17编辑  收藏  举报