1. call 和 apply的区别

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

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

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

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

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

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

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

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

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

2. call和apply的用途

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

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

实际相当于:

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

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

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

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

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

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

我们通过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实现,通常我们会把它实现的更为复杂一点,使得可以往函数中预定义一些参数。

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

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

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

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

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

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

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

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

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

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

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

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

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

技术
©2019-2020 Toolsou All rights reserved,
车主无忧:为什么放弃开源Kafka?必传之作!Alibaba内部出品Redis深度笔记及源码宝典统信UOS首次公布软件适配:QQ、微信、迅雷都有了阿里开发10年技术核心总结,Springboot+Redis文档,送给努力上进的程序员python画爱心Docker容器数据卷详解(共享数据)为何华为的5G专利高居第一名,却还被高通要求缴纳专利费?Spring循环依赖三级缓存是否可以减少为二级缓存?C#中字典的排序方法面试还不会Spring?阿里P8总结的100道面试解析,让你实锤面试官