不禁狂喜面试官让我手搓一个call函数

49 阅读5分钟

前言

听说手搓一个mycall函数模拟一下call函数的功能是几乎每一个前段人在面试时会被问到的问题,现在作者就来挑战一下如何手搓一个mycall函数

call函数的主要功能

要模拟一个call函数,我们要先了解一下call函数究竟做了什么。以下是es5版本官方对于 Function.prototype.call的定义 1764482007812_2ABA918D-4683-4df1-A5E2-145386A1C3DF.png

  • wtf?这是人类写的?
  • 看不懂了,放弃。
images (1).jpg 算了反正只是大概模拟一下call函数的功能,咱们先找一找call函数大概会实现什么功能有什么特点吧。

1. 修改this指向,并触发函数

调用call函数时,可以将调用者内部的this定向绑定到传入的目标对象上

  • 如果传入对象为空,则this指向全局对象(浏览器环境下是window,Node环境下是global)
  • 如果传入对象非空,则将this指向该对象

1764482813906_33D28E1C-79CB-4191-B878-482A40682E5D.png

2. 帮助原函数接收参数

call函数还可以帮助原函数接收参数,并且返回函数执行的结果

3f2ace96b184744f19545a077913423a.png

3. 所有Function类型对象都可以调用

正因为call函数是Function.prototype的一个属性,且所有函数类型对象都是由Function创建的,依据原型链的原则,所以所有函数都可以访问call函数。

  • call函数存在于Function对象显式原型上的证据

1764480489232_BAD3F9A0-A12A-4a49-95AD-D588722403EB.png

myCall函数的实现

1. 实现myCall函数被所有函数调用

call函数可以被所有函数调用是因为你被定义在Function的显式原型上,相同的我们得function函数上添加myCall函数

Function.prototype.myCall=function(){}

2. 实现修改this指向:隐式绑定原则

this隐式绑定原则

当函数被上下文对象引用,且被该对象调用时,函数中的this会绑定到上下文对象上。

call函数的关键功能就是修改原函数内部的this指向,而利用this的隐式绑定原则便可以实现 实现步骤:

  • 目标对象为空,则目标对象为全局对象(window/global)
  • 将myCall的调用者作为属性添加进传入目标对象obj
  • 利用目标对象obj调用原函数,即可实现this改变至目标对象
  • 当然了我们还需要删除这个属性,复原传入的obj对象

1764485103403_934F5635-8078-492a-869C-9689CD8D5A5B.png

注意事项:利用symbol类型数据防止属性覆盖

如果context对象中原先存在名为fn的属性,则会发生覆盖现象。所以我们可以利用symbol定义一个独一无二的属性名进行属性添加,防止覆盖对象中的原有属性

72872d8c1fa49e04d9914e2b16a76a3b.png
Function.prototype.myCall=function(context){
    context=context||window;
    const fn=Symbol('fn'); 
    context[fn]=this;
    context[fn]();
    delete context[fn]
};

3. 实现接收参数:rest参数

call函数调用过程中可以帮助原函数接收参数,但是这个参数数量是不固定的,call函数也无法知道它的调用者究竟要接收多少个形参。

rest参数

因为我们无法得知原函数存在多少个形参,而利用rest参数可以回避这个问题。 rest参数会将函数多接收的元素打包成数组接收,只要对数组进行解构就可以使得所有参数被正确的传给mycall函数内部,进而传给原函数 图解:

1764487259739_E3726F9F-648B-4973-B9FE-10AF430D2A4F.png

修改后的版本

利用rest参数,实现我们设计的myCall函数可以接收不定量的形参

Function.prototype.myCall=function(context,...args){
    context=context||window;
    const fn=Symbol('fn'); 
    context[fn]=this;
    context[fn](...args);
    delete context[fn]
};

最终版本

也许原函数存在执行结果,所以不要忘记接收原函数执行的结果,通过我们设计的myCall函数返回

Function.prototype.myCall=function(context,...args){
    context=context||window; // global
    let fn=Symbol('fn') //创建一个独一无二的函数名,防止往context对象添加属性时覆盖已有数学
    context[fn]=this;//将调用myCall的函数添加进context对象
    const res=context[fn](...args) //通过context对象调用函数,使得函数中的this隐式绑定到context上,并接收最后的结果
    delete context[fn]  //删除前几步添加进context中的函数
    return res  //返回执行结果
};

完结撒花

lQDPM4kJq4e6bvPNBQDNBQCwHltuL29z7u4JBouO0SfnAA_1280_1280.jpg