听说会做这道题的人后来都进了头条?
写在前面
在面试的过程中,相信好多朋友都经历过一些百思不得其姐的题目,或难题,或怪题,或偏题。今天咱们一起来看一道相对偏、但其实又很基础的面试题。这道题是我的一个哥们儿,在半年前去面试字节跳动广州分公司的时候遇到的。他说当时不会做,回来后和分享的(其实是请教嘿嘿嘿~)。
知识预备
作为专业的切图仔,我们对于
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方法。因为它能让一个不拥有某个方法的对象,去借用其他对象的方法来调用 ,所以在很多库和框架源码中的出镜率都是非常高的。
引出问题
OK,
Function#call方法的介绍完毕。下面就来看看今日头条的面试官小哥当初到底问了个什么问题,竟然让我的哥们儿百思不得其姐?
题目是这样的:
以下代码是可以正常执行的:
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);
请问这是什么原因呢?
把
Function#call赋值到变量再调用,之前确实没有这样使用过,但是直觉告诉我这段代码是有些问题,于是立马在 chrome 的控制台看看执行结果:
啊!
call is not a function? 这是在逗我嘛?于是赶紧用
typeof操作符来验证一下到底是不是
function
此时此刻真想骂人了,有木有? 调用
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#call,
Function#apply,
Function#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
或许改成提议那样确实会优雅一些。
另外,如果我们在平时工作中或面试中,遇到一些百思不得其姐的问题,不要凭空去猜测,要透过本质和原理去分析问题的根源。
还有后话
标题只是开玩笑的啦,如果你会做这道题目不一定能进头条,哈哈哈。但是如果不会做,就说明基本功不够扎实,就很有可能过不了面试啦。
- 头条(B)架构演进,阿里(A)服务网格,腾讯(T)分布式数据库,BAT谁更靓丽?
- 在asp.net里创建CrystalReportViewer时老是出错,后来在这里查了些资料才搞定要在web.config中设置如下
- 听说这年头甲方才是王道
- 在一个人类生存的环境里,一个最先改变自己的人最聪明,后来改变自己的人还可以,而一个从来不改变自己的人是最糟糕的。
- 听说新网被黑了
- IT头条:PS3销售量猛增2500% - DoNews.com
- 亮点摄像头驱动 共享给大家 听说很多人在找
- 昨天我QJ了一个空姐, 后来她说我JB很大
- 每日一句听说学英语(周二)
- 每日一句听说学英语(周一)
- 每日一句听说学英语(周一)
- 每日一句听说学英语(周五)
- Count(*) 为0的问题研究 --- 后来发现问题终于可以重现了,参考我后面的关于merge的文章
- 5月14日外电头条:Fedora 11 Leonidas十大新功能
- 很早就听说这个论坛了
- 听说不错的js 广告浮动
- 想在函数中传递CString 类型的数组,后来采用CStringArray类来传递
- 听说了发贴有积分,试试水
- 从前有个男人,不会修电脑,后来的事大家都知道了;从前有个局长,不会用微博,后来的事大家也都知道了。 他是谁?
- 后来朋友选了"伪原创工具下载"这个词