call,apply,bind用法及原理详解

前言

在js中,全部的函数在被调用的时候都会默认传入两个参数,一个是this,还有一个是arguments。在默认状况下this都是指当前的调用函数的对象。可是有时候咱们须要改变this的指向,也就是说使函数能够被其余对象来调用,那么咱们应该怎样作呢?这时候咱们就可使用call,apply和bind方法了。
那么call,apply和bind来自哪里?
在js中全部的函数都是Function的实例,并且对于Function来讲,它的原型即Function.prototype中含有不少东西,其中call,apply和bind方法就是Function原型中的方法,因此根据原型的规则,全部的函数均可以使用原型中属性和方法,因此来讲,对于全部的函数均可以使用call,apply和bind方法。数组

1、方法定义

apply方法

使用 apply, 你能够继承其余对象的方法:app

注意这里apply()的第一个参数是null,在非严格模式下,第一个参数为null或者undefined时会自动替换为指向全局对象,apply()的第二个参数为数组或类数组。函数

function fruits() {}
fruits.prototype = {
    color: "red",
    say: function() {
        console.log("My color is " + this.color);
    }
}
var apple = new fruits;
apple.say();    //My color is red

可是若是咱们有一个对象banana= {color : "yellow"},咱们不想对它从新定义say方法,那么咱们能够经过callapplyapplesay方法:ui

banana = {
    color: "yellow"
}
apple.say.call(banana);     //My color is yellow
apple.say.apply(banana);    //My color is yellow

因此,能够看出callapply是为了动态改变this而出现的,当一个object没有某个方法(本案例中banana没有say方法),可是其余的有(本案例中applesay方法),咱们能够借助callapply用其它对象的方法来操做this

call方法

call()是apply()的一颗语法糖,做用和apply()同样,一样可实现继承,惟一的区别就在于call()接收的是参数列表,而apply()则接收参数数组。prototype

bind方法

bind()的做用与call()和apply()同样,都是能够改变函数运行时上下文,区别是call()和apply()在调用函数以后会当即执行,而bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供咱们须要时再调用。code

this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 由于在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind()方法会建立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以建立它时传入bind()方法的第一个参数做为this,传入bind()方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。对象

2、call,apply,bind区别

一、call的arg传参需一个一个传,apply则直接传一个数组。继承

function hello(name,age){
  console.log(name);
  console.log(age);
}
hello.call(this,"tsrot",24);
hello.apply(this,["tsrot",24]);

二、call和apply直接执行函数,而bind须要再一次调用。get

var obj = {
    x: 81,
};
var foo = {
    getX: function() {
        return this.x;
    }
}
console.log(foo.getX.bind(obj)());  
console.log(foo.getX.call(obj));    
console.log(foo.getX.apply(obj));

三、如何选用

  • 若是不须要关心具体有多少参数被传入函数,选用apply();
  • 若是肯定函数可接收多少个参数,而且想一目了然表达形参和实参的对应关系,用call();
  • 若是咱们想要未来再调用方法,不需当即获得函数返回结果,则使用bind();

3、运用场景

一、实现继承

function Animal(name) {
  this.name = name;
  this.showName = function () {
    console.log(this.name);
  }
  }
function Cat(name) {
  Animal.call(this, name); 
  }
var cat = new Cat('Black Cat');
cat.showName();

二、数组追加

var array1 = [1 , 2 , 3, 5];
var array2 = ["xie" , "li" , "qun" , "tsrot"];
Array.prototype.push.apply(array1, array2);
console.log(array1);

三、获取数组中的最大值和最小值

var num = [1,3,5,7,2,-10,11];
var maxNum = Math.max.apply(Math, num);
var minNum = Math.min.apply(Math, num);
console.log(maxNum); 
console.log(minNum);

四、将伪数组转化为数组

var fakeArr = {0:'a',1:'b',length:2};
var arr1 = Array.prototype.slice.call(fakeArr);
console.log(arr1[0]); 
var arr2 = [].slice.call(fakeArr);
console.log(arr2[0]); 
arr1.push("c");
console.log(arr1);

五、保存this变量

var foo = {
    bar : 1,
    eventBind: function(){
        var _this = this ;
        $('.someClass').on('click',function(event) {
            console.log(_this.bar);     
        });
    }
}
var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            console.log(this.bar);      
        }.bind(this));
    }
}

4、手写call,apply及bind函数

首先从如下几点来考虑如何实现这几个函数

  • 不传入第一个参数,那么上下文默认为window
  • 改变了this指向,让新的对象能够执行该函数,并能接受参数

先来实现call

Function.prototype.myCall = function(context) { 
    if (typeof  this !== 'function') { 
        throw  new  TypeError('Error') 
    } 
    context = context || window ;
    context.fn = this  ;
    const args = [...arguments].slice(1); 
    const result = context.fn(...args) ;
    delete context.fn ;
    return result ;
}

如下是对实现的分析:

  • 首先context为可选参数,若是不传的话默认上下文为window
  • 接下来给context建立一个fn属性,并将值设置为须要调用的函数
  • 由于call能够传入多个参数做为调用函数的参数,因此须要将参数剥离出来
  • 而后调用函数并将对象上的函数删除

以上就是实现call的思路,apply的实现也相似,区别在于对参数的处理,因此就不一一分析思路了

Function.prototype.myApply = function(context) { 
        if (typeof  this !== 'function') { 
            throw  new  TypeError('Error') 
        } 
        context = context || window ;
        context.fn = this;  
        let result // 处理参数和 call 有区别  
        if (arguments[1]) { 
            result = context.fn(...arguments[1]) 
        } else { 
            result = context.fn() 
        } 
        delete context.fn ;
        return result; 
    }

bind的实现对比其余两个函数略微地复杂了一点,由于bind须要返回一个函数,须要判断一些边界问题,如下是bind的实现

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回一个函数
    return function F() {
        // 由于返回了一个函数,咱们能够 new F(),因此须要判断
        if (this instanceof F) {
        return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}

如下是对实现的分析:

  • 前几步和以前的实现差很少,就不赘述了
  • bind返回了一个函数,对于函数来讲有两种方式调用,一种是直接调用,一种是经过new的方式,咱们先来讲直接调用的方式
  • 对于直接调用来讲,这里选择了apply的方式实现,可是对于参数须要注意如下状况:由于bind能够实现相似这样的代码f.bind(obj, 1)(2),因此咱们须要将两边的参数拼接起来,因而就有了这样的实现args.concat(...arguments)
  • 最后来讲经过new的方式,对于new的状况来讲,不会被任何方式改变this,因此对于这种状况咱们须要忽略传入的this

将不断更新完善,期待您的批评指正!

相关文章
相关标签/搜索