JavaScript中的 Call 和 Apply

1. call 和 apply的区别

Function.prototype.call 和 Function.prototype.apply都是非常常用的方法,它们的作用一模一样,区别仅在于传入参数形式的不同。

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

JavaScript中的 Call 和 Apply_第1张图片

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

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

JavaScript中的 Call 和 Apply_第2张图片

当调用一个函数时,JavaScript的解释器并不会计较形参和实参在数量、类型以及顺序上的区别,JavaScript的参数在内部就是用一个数组来表示的,从这个意义上来说,apply比call的使用率更高,我们不必关心具体有多少参数被传入函数,只要用apply一起推过去就完事了。

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

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

JavaScript中的 Call 和 Apply_第3张图片

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

JavaScript中的 Call 和 Apply_第4张图片

有时候我们使用call或者apply的目的不在于指定this指向,而是另有有途,比如借用其也对象的方法,那么我们可以传入null来代替某个具体的对象:

JavaScript中的 Call 和 Apply_第5张图片

2. call和apply的用途

(1). 临时改变this的指向,这是它们最常见的用途。下代码可以用来说明:

JavaScript中的 Call 和 Apply_第6张图片

当执行getName.call(user1)这行代码时,getName函数体内的this就指向user1对象,所以此处的:

JavaScript中的 Call 和 Apply_第7张图片

实际相当于:

JavaScript中的 Call 和 Apply_第8张图片

在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div的节点,div的节点的onclick事件中的this本来是指向这个div的。

JavaScript中的 Call 和 Apply_第9张图片

假如该事件函数中有一个内部函数func,在事件内部调用这个函数时,func函数体内的this就指向了window,而不是我们预期的div,请看如下代码:

JavaScript中的 Call 和 Apply_第10张图片

这个时候我们可以用call来修正func函数的指向this,使其依然指向div。

JavaScript中的 Call 和 Apply_第11张图片

另外在本博客的"JavaScript中this的理解"也用apply来修正this,代码如下:

JavaScript中的 Call 和 Apply_第12张图片

(2). 永久绑定的 this 的 bind

大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向,如下所示:

JavaScript中的 Call 和 Apply_第13张图片

我们通过Function.prototype.bind来包装func函数,并且传入一个对象context作为参数,这个context对象就是我们想修正的this对象。

在Function.prototype.bind的内部实现中,我们先通过 var that=this 这行代码把func函数的引用保存起来,然后返回一个新的函数。当我们在执行func这个函数时,实际上先执行的是这个刚刚返回的新函数。在新函数的内部,that.apply(context , arugments) 这行代码才是执行的原来的func函数,并且指定context对象为func函数体内的this。

这是一个简化版的Function.prototype.bind实现,通常我们会把它实现的更为复杂一点,使得可以往函数中预定义一些参数。

JavaScript中的 Call 和 Apply_第14张图片

(3). 改变this借用其他对象的方法

借用方法的第一种场景是"借用构造函数",通过这种技术,可以实现一些类似继承的效果。

JavaScript中的 Call 和 Apply_第15张图片

借用的第二种方法运用的场景跟我们的关系更加的密切。

函数的参数列表arguments是一个类数组对象,虽然它也有"下标",但它并不是真正的数组,所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素,这种情况下,我们常常会借用Array.prototype对象上的方法。 比如想往arguments中添加一个新的元素,通常会借用Array.prototype.push。  

JavaScript中的 Call 和 Apply_第16张图片

在操作arguments的时候,我们经常会频繁地找Array.prototype对象借用方法。

想把arguments转成真正的数组的时候,可以借用Array.prototype.slice的方法;想截取arguments列表中的头一个元素时,以可以借用Array.prototype.shift方法。那么这种机制的内部实现原理是什么呢?以Array.prototype.push为例,看看V8引擎中是如何实现的。

JavaScript中的 Call 和 Apply_第17张图片

通过这段代码可以看到,Array.prototype.push 实际上是一个属性复制的过程,把参数按照下标依次添加到被push的对象上面,顺便修改了这个对象的length属性,至于修改的对象是数组还是类数组对象并不重要。

可以看出来,Array.prototype.push并不是数组的专属,对象也可以借用。

JavaScript中的 Call 和 Apply_第18张图片

上述代码在大部分浏览器中都可以正常跑通,如果在低版本的IE浏览中执行,必须显示的给对象obj设置length属性:

var obj = {} ; obj.length=0 ;

大家都在知道,在JavaScritp中一切皆对象,但并不是所有的对象都可以借用其它对象的方法,就像我和马云都是中国人,但我却不可能向他借到钱一样,以Arry.prototype.push方法为例,要借用到此方法,必须要满足两个条件:

(1). 对象本身要可以存取属性,像number和str类型的数字是绝对不可能借到这个方法的。

(2). 对象本身的length属性要可写,如果借用此方法的对象是一个function,就会产生报错。

你可能感兴趣的:(JavaScript)