悟透前端 | 参悟 Javascript 中的 call 和 apply

对于前端工程师来说, call apply 算是常用的函数方法,允许通过函数和在函数调用中指定 this 的指向。那么这两个方法到底有什么区别呢?本文将详细介绍这两个方法,顺便加深对其理解。

call

方法使用一个指定的  this  值和单独给出的一个或多个参数来调用一个函数。允许为不同的对象分配和调用属于一个对象的函数/方法。提供新的  this  值给当前调用的函数/方法。你可以使用  call  来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。



constobj = {
domain:"www.infoq.cn",
};
functionsiteurl(protocol, path){
console.log([protocol,this.domain, path].join(""));
}
siteurl.call(obj,"https://","/u/devpoint");// https://www.infoq.cn/u/devpoint

语法

function .call(thisArg, arg1, arg2, …)



  • thisArg :可选的。

在  function  函数运行时使用的  this  值。请注意, this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为  null  或  undefined  时会自动替换为指向全局对象,原始值会被包装。

  • arg1, arg2, ... :指定的参数列表。

返回值

使用调用者提供的  this  值和参数调用该函数的返回值。若该方法没有返回值,则返回  undefined

apply



apply()  方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。在调用一个存在的函数时,你可以为其指定一个  this  对象。  this  指当前对象,也就是正在调用这个函数的对象。 使用  apply , 可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

语法

func.apply(thisArg, [argsArray]);



  • thisArg :必选的。

在  func  函数运行时使用的  this  值。请注意, this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为  null  或  undefined  时会自动替换为指向全局对象,原始值会被包装。

  • argsArray :可选的。

一个数组或者类数组对象,其中的数组元素将作为单独的参数传给  func  函数。如果该参数的值为  null  或   undefined ,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。 浏览器兼容性 请参阅本文底部内容。

返回值

调用有指定 this 值和参数的函数的结果。

call和apply的区别

它们的作用一模一样,区别仅在于传入参数形式的不同。





apply  接受两个参数,第一个参数指定了函数体内  this  对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组, apply  方法把这个集合中的元素作为参数传递给调用的函数:

constfunc =function(a, b, c){
console.log([a, b, c]);
};
func.apply(null, [1,2,3]);// [ 1, 2, 3 ]



这段代码中,参数 1、2、3 被放在数组中一起传入 func 函数,它们分别对应 func 参数列表中的 a 、b 、c

call  传入的参数数量不固定,跟  apply  相同的是,第一个参数也是代表函数体内的  this  指向,从第二个参数开始往后,每个参数被依次传入函数:

constfunc =function(a, b, c){
console.log([a, b, c]);
};
func.call(null,1,2,3);// [ 1, 2, 3 ]

调用一个函数时,Javascript 的解释器并不会计较形参和实参在数量、类型以及顺序上的区别,Javascript 的参数在内部就是用一个数组来表示的。



从这个意义上说, apply  比  call  的使用率更高,不关心具体多少参数被传入函数,只要用  apply  一股脑地推过去就可以了。



call  是包装在  apply  上的一颗语法糖,如果明确地知道函数接收多少个参数,而且想一目了然地表达形参和实参的对应关系,那么可以用  call  来传送参数。



当使用 call 或者 apply 的时候,如果传入的第一个参数为 null ,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window



const func = function(a,b,c){
console.log(this === window );
}
func.apply(null,[1,2,3]); // true



如果是在严格模式下,函数体内的 this 还是为 null :

constfunc =function(a, b, c){
console.log(this===window);
};
func.apply(null, [1,2,3]);// true

改变this指向

constobj = {
site:"https://www.infoq.com",
};
functionfunc(){
console.log(this.site);
}
func.call(obj);// https://www.infoq.com



call  方法的第一个参数是作为函数上下文的对象,上面的代码把  obj  作为参数传给了  func ,此时函数里的  this  便指向了  obj  对象。此处  func  函数相当于下面的方法:

constobj = {
site:"https://www.infoq.com",
};
functionfunc(){
console.log(obj.site);
}

apply()方法的一些妙用



Math.max  可以实现得到数组中最大的一项



因为 Math.max 不支持 Math.max([param1,param2]) 也就是数组,但是它支持 Math.max(param1,param2...) ,所以可以根据 apply 的特点来解决  var max=Math.max.apply(null,array) ,这样就轻易的可以得到一个数组中的最大项( apply 会将一个数组转换为一个参数接一个参数的方式传递给方法)这块在调用的时候第一个参数给了 null ,这是因为没有对象去调用这个方法,我只需要用这个方法帮我运算,得到返回的结果就行,所以直接传递了一个 null 过去。



用这种方法也可以实现得到数组中的最小项: Math.min.apply(null,array)



Array.prototype.push 实现两个数组的合并



同样 push 方法没有提供 push 一个数组,但是它提供了 push(param1,param2...paramN) ,同样也可以用 apply 来转换一下这个数组,即:

consttarget =newArray("1","2","3");
constsource =newArray("4","5","6");
constresult =Array.prototype.push.apply(target, source);// 得到合并后数组的长度,因为push就是返回一个数组的长度
console.log(target);// [ '1', '2', '3', '4', '5', '6' ]
console.log(source);// [ '4', '5', '6' ]
console.log(result);// 6