写在前面
在JavaScript中call和apply这两个函数是比较基础的东西。因为一般的前端开发中用不到,所以初入Web前端的程序员们并不是很清楚他们的用法、联系及区别。爱钻研探索的程序员,或是已经在前端开发中渐入佳境者多少会听说或用到这两个函数,所以网上关于他们的介绍并不在少数。我在写这篇之前也在网上搜索了一番,虽然大多数为复制粘贴相互转载,但也有不少作者比较用心,写的确有独到之处。当然我对于call和apply以及他们的出身用途也都有自己的理解。
从何而来
站在面向对象的角度来说,在JavaScript中所有使用function关键字定义的方法或函数,都是Function对象(类)的一个实例对象。对象可以拥有自己的方法和属性,而call和apply正是所有函数对象都具有的方法。请看下面的代码段:
// 下面是使用两种不同的方式定义函数了四个函数
> function fun1(str){console.log("fun1 say "+str)}
> fun2 = new Function("str","console.log('fun2 say '+str)");
> function fun3(a,b){console.log(a+b)}
> fun4 = new Function("a","b","console.log(a+b)");
//下面是对各个函数对象的call方法进行测试,通过代码大家不难发现,
//所有函数对象的call方法都是相等的
> fun1.call === Function.prototype.call
< true
> fun2.call === Function.prototype.call
< true
> fun3.call === Function.prototype.call
< true
> fun4.call === Function.prototype.call
//下面是对各个函数对象的apply方法进行测试,通过代码大家不难发现,
//所有函数对象的apply方法都是相等的
> fun1.apply=== Function.prototype.apply
< true
> fun2.apply=== Function.prototype.apply
< true
> fun3.apply=== Function.apply
< true
> fun4.apply=== Function.apply
//下面是对Function类(其实也是一个函数对象)的一些测试,
//通过代码我们发现Function原型prototype的call方法与Function的call方法相等,
//且Function原型prototype的apply方法与Function的apply方法相等
> Function.prototype.call === Function.call
< true
> Function.prototype.call === Function.call
< true
通过上面的代码段我们可以了解到,Function.prototype,Function,以及所有函数对象的call和apply方法都相等的,也就是说这些对象中的这两个方法其实本质上是只两个方法在被他们分别继承反复重用了而已。
在JavaScript中,所有“函数对象”都是Function
类的实例,无论这个函数对象是使用new Function()
的方式定义,还是使用function
关键字定义;包括Object
类、甚至连Function
类自身也都是Function
类的对象,我们可以统称他们为“函数对象”。而call和apply这两个方法就是在Function.prototype中定义的方法,所以会在所有的函数对象中通过原型链从Function.prototype中得到继承。这就是call和apply的由来。
作用
call和apply的作用是相同的,都是为了改变函数运行时的上下文。说上下文可能不太好理解。下面通过一段代码来解释:
好长的一段代码,如果你已经理解了,可以勇敢点跳过你认为啰嗦的那部分代码
//随便定义两个函数吧
function a(){
console.log(this.val);
}
function b(){
var str = "wfso";
console.log(str);
}
//下面是对a和b两个函数几种不同的调用方式
// 通过下面的两小段代码,可以知道,函数的默认上下文是window,也可以说是global吧
> a();
< undefined
> var val = "val";
< a();
< val
// 对b的调用只是作为与a的对比
> b();
< wfso
// 创建一个对象w,然后把w当作a函数对象的call和apply方法的参数
// 改变a函数的执行上下文
> var w={val:"仵士杰"};
> a.call(w);
< 仵士杰
> a.apply(w);
< 仵士杰
// 为了有更清晰的结果,再创建一个对象v来做实验吧
> var v={val:"www"};
> a.call(v);
< www
> a.apply(v);
< www
// 同样作为对比,我们分别对b也做同样的调用看看
> b.call(w);
< wfso
> b.apply(w);
< wfso
> b.call(v);
< wfso
> b.apply(v);
< wfso
如果我现在说,函数的执行上下文其实就是在函数中this关键字引用的对象,你应该不会有异议了吧。全局函数的默认上下文是window。而类中方法的默认上下文是调用这个方法的对象。如果你要改变这种默认的函数或方法执行时的上下文件,就需要通过函数对象的call或apply方法来实现了。
区别和用法 call OR apply
call 与 apply 在作用上是完全相同的,他们的区别体现调用时的参数传递上。
call 调用方法
调用call时如果需要参数传递,则需要把所有参数一一列出来。如下:
fun.call(ctx[,arg1[,arg2[,arg2[,……]]]]);
arg1,arg2,arg3是给fun函数传的参数列表。
function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.call(v,"仵士杰","xt");
< wfs
< 仵士杰--xt
apply 调用方法
调用apply时如果需要参数传递,则需要把所有参数放到一个数组里,然后把所有参数组成的数组作为一个参数传递给apply方法。如下:
fun.apply(ctx[,arguments]);
arguments是一个数组,里面存储的是给fun函数传的参数列表。
function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.apply(v,["仵士杰","xt"]);
< wfs
< 仵士杰--xt
扎然而止
好了,上面的介绍已经是我能力的极限了,不想写什么总结了。就这样结束吧,打完收功!