JavaScript中call和apply的使用

在ES3中给Function的原型定义了2个方法,分别是Function.prototype.call和Function.prototype.apply,在我们的项目开发中,尤其是在一些函数式风格的代码编写过程中,call和apply的作用显得格外重要。下面就让我们来看看这两个方法的作用吧!

call和apply的区别

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

apply接收两个参数,第一个参数指定了函数体内this的指向,第二个参数是一个集合,可以是一个数组,也可以是一个类数组,apply将这个集合作为参数传递给被调用的函数。

var fun = function(a, b, c, d) {
    alert([a, b, c, d]);     // [1, 2, 3, 4]
}

fun.apply(null, [1, 2, 3, 4]);

call接受的参数是不固定的,第一个参数和apply一样,不同的是从第二个参数开始之后,依次传递给被调用的函数。

var fun = function(a, b, c, d) {
    alert([a, b, c, d]);     // [1, 2, 3, 4]
}

fun.apply(null, 1, 2, 3, 4);

当我们在调用一个函数时,其JavaScript解释器并不在意形参和实参在数量、类型和顺序上的区别,因为JavaScript的参数在内部其实就是一个数组。从这种意义上来说,apply就会比call的效率更高,因为我们不必在担心需要传递参数的数量,直接使用apply往里推就是了,当然,如果想要很清楚的查看形参和实参的对应关系,也是可以使用call的。

在使用call和apply的时候,如果我们传入的第一个参数为null,则this会指向默认的宿主对象,在我们的浏览器中也就是指向window。

var fun = function(){
    console.log(this === window);  // true
};

fun.apply(null, [1, 2, 3]);

但是在ES5的严格模式下,函数体内的this依然为null。

var fun = function(){
    "use strict";
    console.log(this === null);  // true
};

fun.apply(null, [1, 2, 3]);

其实有的时候我们使用call或者apply并不是为了指定this的指向,而是为了借用其他对象的方法。

Math.max.apply(null, [1, 5, 4, 3, 3])   //    5

call和apply的用途

下面就让我们来看看call和apply的一些用途

1. 改变this的指向

我们在使用call和apply最常用的例子就是来改变this的指向,来看看下面的例子:

window.name = 'iFuhang';
var obj1 = {
    name : 'smile'
};
var obj2 = {
    name : 'tom'
};
var getName = function(){
   console.log(this.name)
}

getName();               //   iFuhang   (this指向window)
getName.call(obj1);  //   smile       (this指向obj1)
getName.call(obj2);  //   tom         (this指向obj2)

当我们在执行getName.call(obj1)的时候,getName内部的this已经指向了obj1,所以最后输出了smile。
在实际的项目开发中,有时会不经意间改变了this的指向,这时我们就可以利用call或者apply来修正this的指向。其实我们在上一章节JavaScript中this关键字的使用中已经看到过类似的例子了,这里我就不在赘述了,感兴趣的小伙伴可以去看看。

2. Function.prototype.bind方法的实现

现在有大部分高级浏览器已经实现了Function.prototype.bind方法的使用,用来指定函数内部this的指向,但是为了兼容那些还没有实现这个方法的浏览器,我们可以模拟出一个Function.prototype.bind方法来。

Function.prototype.bind = function(context) {
    var self = this;      // 保存原函数,也就是调用bind方法的函数
    return function(){
        return self.apply(context, arguments);      // context指最后需要指向的那个对象,此案例中表示obj
    }
}

var obj = {
    name : 'iFuhang'
}

var getName = function(){
    console.log(this.name);     // iFuhang
}.bind(obj);

getName();

其实这里我们通过Function.prototype.bind来”包装“了getName函数,同时传入了一个context参数,这个参数就是我们最后需要给this指定的对象。

在Function.prototype.bind函数内部,我们首先保存了原函数,然后返回了一个新函数,在我们执行getName函数时,其实首先执行的时刚刚返回的新函数,然后在返回的新函数内部,self.apply(context, arguments)这句话才是我们最后执行的getName 函数,同时也将this的指向绑定到了context上了,也就是案例中的obj上面。

其实上面还只是一个简单的模拟,我们还可以是实现一个稍微复杂一些的bind方法。

Function.prototype.bind = function(){
    var self = this;
    var context = [].shift.call(arguments),     // 从参数列表中取出第一个参数,也就是最后this需要指向的那个对象
          args = [].slice.call(arguments);          // 取出参数列表中剩余的参数并转换为数组
    return function(){
        return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
                         //  合并从bind方法中去除的数组args和self函数中传进来的数组
    }
}

var obj = {
    name : 'iFuhang'
}

var getName = function(a, b, c, d){
    console.log(this.name);     // iFuhang
    console.log([a, b, c, d]);     // [2, 3, 4, 5]
}.bind(obj,  2, 3);

getName(4, 5);

3. 借用其他对象的方法

我们还可以通过call和apply来借用其他对象的方法。

借用方法的第一种场景就是”借用构造函数“,通过这种方式,我们可以实现类似继承的效果:

var A = function(name){
    this.name = name;
}

var B = function(){
    A.apply(this, arguments);
}

B.prototype.getName = function(){
    return this.name;
}

var b= new B('iFuhang');
console.log(b.getName());     // iFuhang

下面这种借用方法我想大家会经常用到。
众所周知,函数的参数列表是一个类数组对象,虽然它也有索引下标和length属性,但他并且是一个正真的数组,所以它没有像数组中有那么多的API可以使用,这时我们就可以借用Array.prototype上的一些方法提供给arguments使用。

function args() {
    Array.prototype.push.apply(arguments, [1, 2, 3]);
    console.log(arguments);    //   [1, 2, 3]
}
args()

其实在操作arguments时,有很多的方法都可以去借用Array.prototype中的方法的,这里就不一一举例了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值