• 听说会做这道题的人后来都进了头条?


    写在前面

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

    知识预备

    作为专业的切图仔,我们对于 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#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
    

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

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

    还有后话

    标题只是开玩笑的啦,如果你会做这道题目不一定能进头条,哈哈哈。但是如果不会做,就说明基本功不够扎实,就很有可能过不了面试啦。

  • 相关阅读:
    ajax提交的javascript代码
    简述jpg、gif、png-8、png-24的区别,分别使用场景
    5种IE hasLayoutt的属性及其值
    EF线程唯一与DBSession接口对接
    报错:未能加载文件或程序集“XXX”或它的某一个依赖项。系统找不到指定的文件
    报错:无法将类型"System.Data.EntityState"隐式转换为"System.Data.Entity.EntityState"
    ASP.NET 之异步处理一(Session处理)
    C#基础之类的出现
    Hadoop
    Hadoop
  • 原文地址:https://www.cnblogs.com/yugege/p/11841767.html
Copyright © 2020-2023  润新知