当前位置: 首页 > Web前端 > JavaScript

JavaScript深入理解系列:call和apply

时间:2023-03-27 16:12:45 JavaScript

定义call和apply:函数call()方法被执行,函数里面的this会指向第一个参数值,除了第一个参数值之后的几个分支都是passedintothefunction,简而言之就是在函数运行的时候改变this点。使用示例:fn.call(obj,args1,args2...),fn.apply(obj,[arg1,arg2...]),call和apply除了第二个参数传递方式不同外其他都是一样的。例如1:varobj={name:'Programmer'};functionfn(){console.log(this.name)}fn()//this.name=>undefinedfn.call(obj)//this.name=>'程序员米粉'总结:1、fn函数调用call方法执行时,函数的this指向obj2,执行call方法和fn函数。如果你还不明白,当执行fn.call(obj)时,可以认为是在obj对象中执行了一个fn函数。那么fn函数中的this点就是obj又如2:varobj={name:'程序员米粉',fn:function(){console.log(this.name)}};obj.fn();//this.name=>'程序员米粉'总结:1.当执行例子1中的fn.call(obj)时,相当于例子2的obj.fn();当call方法被调用时,会改变当前调用函数的执行上下文(也就是改变this的指向)。SimulationStep1Example1:varobj={name:'ProgrammerRice'};functionfn(){console.log(this.name)}fn.call(obj)//this.name=>'ProgrammerRice'例子2:varobj={name:'程序员米粉',fn:function(){console.log(this.name)}};obj.fn();//this.name=>'程序员米粉'实现自己的myCall方法,首先观察例1和例2中测得的obj对象的区别,区别:1、例2中obj增加了fn函数方法。2、例2中的执行方式为obj.fn()执行,而例1中的执行方式为fn.call(obj)。这个好办,我们怎么把例1改成例2呢?下面总结一下步骤:1.将fn函数设置为对象的一个??属性2.调用fn函数执行。3.调用完成后,从对象中删除fn属性(函数)。(给对象添加属性,我们调用后删除即可)有了思路,我们可以这样写代码:obj.fn=fn;obj.fn();deleteobj.fn;//按照上面的思路删除属性,然后按照这个思路写自己的myCall方法:Function.prototype.myCall=function(context){context.fn=this;上下文.fn();deletecontext.fn;};varobj={name:'程序员米粉'};functionfn(){console.log(this.name);}fn.myCall(obj)//this.name=>'程序员米粉'````执行myCall方法,可以得出this指向obj对象,并打印期望值,至此该方法第一步完成!#Simulationstep2继续改进myCall方法,更改fn方法,增加2个参数,再次执行该方法。functionfn(index,value){console.log(this.name);console.log(index,value);}fn.myCall(obj,111,'我是一个值');//this.name=>'程序员米粉'//undefined,undefined执行一个fn函数,只打印出一个值,传入参数,不打印出来。那么就需要修改一下方法,让参数也能打印出来。我们可以从arguments对象中获取值,arguments对象代表函数传入的对象,打印出来看看。Function.prototype.myCall=function(context){console.log(arguments);context.fn=this;context.fn();deletecontext.fn;};打印出的arguments对象:![](/img/bVcY9cV)我们可以看到arguments对象结构为:{'0':{name:'程序员米粉'},'1':111,'2':'我是一个值'}0代表传入的第一个参数,1代表第二个参数,以此类推。我们看到arguments是一个类数组,所以我们可以使用数组的方法来存储它。由于我们只获取参数,所以我们从1开始取值。varargs=[];for(vari=1;i4,最终输出为2console.log(eval('context.fn('+args+')'))//相当于运行context.fn(arguments[1],arguments[2],...),使用eval执行一串JavaScript语句。+[eval更详细链接;](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval)args会自动调用Array.toString()方法。通过eval自动执行就变成了context.fn(arguments[1],arguments[2])这样执行。代码如下:varobj={name:'程序员米饭'};函数fn(index,value){console.log(this.name);//程序员米饭console.log(index,value);//111i是一个值}Function.prototype.myCall=function(context){context.fn=this;变量参数=[];对于(vari=1;i'程序员米粉'执行方法,输入完全符合我们的预期,我们终于搞定了!#模拟步骤3##第一个参数传null或undefined,this指向window例如:varname='程序员米粉'functionfn(){console.log(this.name);//程序员米粉}fn.call(null);//this.name=>'程序员米粉'fn执行的时候,还是输出this.name='程序员米粉',什么意思,只要第一个参数传null或者undefined,函数调用call方法,this指向window##函数执行call方法,如果有返回值,则返回。例如:varname='程序员米饭'functionfn(){console.log(this.name);//程序员米饭return{name:this.name}}fn.call(null);//this.name=>'程序员米粉'//执行fn函数,直接返回对象//{//name:'程序员米粉'//}##最终版实现调用代码varobj={name:'程序员米粉'};functionfn(index,value){console.log(this.name);//程序员米饭console.log(index,value);//111我是一个值return{name:this.name,index:index,value:value};}Function.prototype.myCall=function(context){varcontext=context||window;context.fn=this;varargs=[];for(vari=1;i'程序员米粉'//最终输出//{//name:"程序员米粉"//index:111//value:"我是一个值"//}#实现applycode由于apply和call的原理基本相同,就不详细描述了,直接添加代码即可:Function.prototype.myApply=function(context,arr){varcontext=context||窗口;context.fn=this;var结果;if(!arr){result=context.fn();}else{varargs=[];对于(vari=0;i