据说会作这道题的人后来都进了头条?

写在前面

在面试的过程当中,相信好多朋友都经历过一些百思不得其姐的题目,或难题,或怪题,或偏题。今天我们一块儿来看一道相对偏、但其实又很基础的面试题。这道题是个人一个哥们儿,在半年前去面试字节跳动广州分公司的时候遇到的。他说当时不会作,回来后和分享的(实际上是请教嘿嘿嘿~)。node

知识预备

做为专业的切图仔,咱们对于 Function.prototype.call 方法确定不陌生(后面简写成 Function#call) ,它的做用是用来指定 this 对象的,或者也能够通俗一点说,是用来让一个对象 借用 另一个对象的某个方法的。好比数组有个方法叫作 Array#push, 而后咱们本身发明了一个假数组(是指有 length 属性的对象), 咱们想要借用真数组的 push 方法来添加元素,可能会写出如下代码:面试

const arrayLike = {
   length: 0
}
[].push.call(arrayLike, 1)
console.log(arrayLike); // {0: 1, lenght: 1}
复制代码

第 4 行中就用到了 Funtion#call 方法。由于它能让一个不拥有某个方法的对象,去借用其余对象的方法来调用 ,因此在不少库和框架源码中的出镜率都是很是高的。chrome

引出问题

OK, Function#call 方法的介绍完毕。下面就来看看今日头条的面试官小哥当初到底问了个什么问题,居然让个人哥们儿百思不得其姐?数组

题目是这样的:浏览器

如下代码是能够正常执行的:bash

const arrayLike = {
   length: 0
}
[].push.call(arrayLike, 1);
console.log(arrayLike); // {0: 1, lenght: 1}
复制代码

可是把若是把 [].push.call 方法赋值到变量后再调用 ,在 chrome 却会报错:服务器

const arrayLike = {
   length: 0
}
const call = [].push.call;
call(arrayLike, 1); 
console.log(arrayLike);
复制代码

请问这是什么缘由呢?app

Function#call 赋值到变量再调用,以前确实没有这样使用过,可是直觉告诉我这段代码是有些问题,因而立马在 chrome 的控制台看看执行结果:框架

啊!call is not a function ? 这是在逗我嘛?因而赶忙用 typeof 操做符来验证一下究竟是不是 function ui

此时此刻真想骂人了,有木有? 调用 call() 会报错 call is not a function , 而使用 typeof 检测 call 的类型倒是 function ! 你说气不气人?

解决问题

冷静冷静!面试遇到不懂的问题必定要冷静,若是紧张就真的不知道怎么办,只能凉拌了。那冷静又能怎么办呢?冷静以后, 就要尝试从从实现原理或源码来寻找思路。

下面就根据 Function#call 基本用法,来本身实现一个叫作 Function#myCall 的方法。

Function.prototype.myCall = function(context, ...args) {
  context = context || {};
  context.fn = this;
  const res = context.fn(...args);
  delete context.fn;
  return res;
}
复制代码

下面来验证一下 myCall 的功能:

const arrayLike = {
   length: 0
}
[].push.myCall(arrayLike, 1)
console.log(arrayLike); // {0: 1, lenght: 1}
复制代码

OK, 是能够正常工做了。

而后,若是把 [].push.myCall 赋值到变量再调用会怎样呢?会不会也跟原生的 call 同样报错?以下:

const arrayLike = {
   length: 0
}
const myCall = [].push.myCall;
myCall(arrayLike, 1);
console.log(arrayLike); 
复制代码

在 chrome 中执行结果以下:

错误信息是 context.fn is not a function, 从源码中咱们能够看到 context.fn 实际上就是 this , 由于源码中有 context.fn = this 的赋值语句。 那这个 this 究竟是什么呢?若是在浏览器端就是 window , 若是在 node 端就是 global,由于他们确实都不是 function 类型。因此调用就会报错。

好听,到此你们应该已经明白了为何 Functio#call 不能先赋值给变量后再调用了。

其余环境

可是再来看另一个问题,chrome 的错误信息 call is not function , 实际上是很是不友好的错误信息,甚至是自相矛盾的。由于他说 call 变量的值不是 function , 而用 typeof 检测倒是 function。因此这道题目的自己其实并不难,而是被 chrome 的极不友好的错误信息给误导了。

下面让咱们来看看其余浏览器或 node 端的错误信息:

  • IE11 浏览器

  • edge 浏览器:

  • firefox 浏览器

  • node 服务器

对比以上不一样的浏览器,会发现,node 服务器 和 chrome 浏览器的错误信息是同样的,都是 call is not a function, 这是由于他们都用了 v8 解释器的缘由。而 IE11 和 edge 的错误信息也基本同样,都是 this is not a Function object。 而 firefox 的错误信息是 Function.prototype.call called on incompatible undefined,这是什么意思呢??我也不知道,因而悄悄的点击了错误信息右边的 [详细了解] ,而后打开了 mdn 上关于 X.prototype.y called on incompatible type 的详情页面, 里面有对这种错误的详细介绍,并列举出了好比 Funtion#callFunction#applyFunction#bind 等, 使用不当都会发生这样的错误。

来个总结

同一个错误在不一样的浏览器上却出现了五花八门的报错信息,能够看出优雅的报错信息也是很难的, 关于 v8 引擎的 issue 6513 就是专门作这件事的。这个 issue 里有人提议:

var c = Function.prototype.call; c();
Uncaught TypeError: c is not a function
复制代码

Should be something like:

c is called with undefined as a context which is not a function
复制代码

或许改为提议那样确实会优雅一些。

另外,若是咱们在平时工做中或面试中,遇到一些百思不得其姐的问题,不要凭空去猜想,要透过本质和原理去分析问题的根源。

还有后话

标题只是开玩笑的啦,若是你会作这道题目不必定能进头条,哈哈哈。可是若是不会作,就说明基本功不够扎实,就颇有可能过不了面试啦。

相关文章
相关标签/搜索