axios源码中apply方法的使用与手写bind

657 阅读3分钟

1、call()

var a = {
    user:"江苏南京",
    fn:function(){
        console.log(this.user); //江苏南京
    }
}
var b = a.fn;
b.call(a);   // 等价于直接执行a.fn();输出“江苏南京”

通过在call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象。

call方法除了第一个参数以外还可以添加多个参数,如下:

b.call(a,1,2);

2、apply()

apply方法和call方法有些相似,它也可以改变this的指向

var a = {
    user:"江苏南京",
    fn:function(){
        console.log(this.user); //江苏南京
    }
}
var b = a.fn;
b.apply(a);  // 等价于直接执行a.fn();输出“江苏南京”

同样apply也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

b.apply(a,[10,1]);

或者

var arr = [500,20];
b.apply(a,arr);

//注意如果call和apply的第一个参数写的是null,那么this指向的是当前所在的全局对象或者window对象(具体看下面的axios源码)

b.apply(null);

call 与 apply 的区别

***call的第二部分参数要一个一个传,apply要把这些参数放到数组中 ***,就这么点区别!

源码案例🌰:

axios 源码中axios.spread()方法使用案例如下:


function f(x, y, z) {
    console.log(x+y+z)
}

var args = [1, 2, 3];

f.apply(null, args);   

// 输出: 6 (相当于f(1,2,3),将数组元素作为参数)

用axios.spread方法用法:

axios.spread(function(x, y, z) {})([1, 2, 3]);

源码实现:

/**

 * @param {Function} callback

 * @returns {Function}

 */

module.exports = function spread(callback) {

  return function wrap(arr) {

    return callback.apply(null, arr);   

  };

};

3、bind()

bind方法和call、apply方法有些不同,但它们都是用来改变this的指向。


var a = {
    user:"江苏南京",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b.bind(a);

我们发现代码没有被打印

var a = {
    user:"江苏南京",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
var c = b.bind(a);
console.log(c); // function() { [native code] }   得到的是函数,而非执行结果

那么我们现在执行一下函数c看看,能不能打印出对象a里面的user

c();  // 江苏南京

一目了然!!! 同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

var c = b.bind(a,10); c(1,2);

总结

*** bind()和call、apply不同。bind是新创建一个函数,然后把它的上下文绑定到bind()括号中的参数上,然后将它返回,并没有直接执行。而只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数。 ***

面试常考手写bind方法

    Function.prototype.bind = function(context) {
        let self = this;  // 原型中的this指向调用它的对象,所以此处要转存
        let args = Array.prototype.slice.call(arguments);
            
        return function() {
            return self.apply(context, args.slice(1));    
        }

关于这段代码let self = this,我们做个解释,看下面的代码:

function a(){};

a.prototype.sing = function(){
    console.log(a.prototype == this);
};

var b = new a();

b.sing();//false

显然,this不指向prototype,而经过测试,它也不指向a,而指向b。 所以*** 原型中的this指向调用它的对象。 ***

Array.prototype.slice.call(arguments); // [1,2,3,4].slice(1) // 输出[2, 3, 4] 上面代码会将一个类数组转化为数组。由于arguments自己没有slice方法,这里属于借用Array原型的slice方法。 如果你不给slice传参数,那就等于传了个0给它,结果就是返回一个和原来数组一模一样的副本。 这之后的代码就很好理解,返回一个函数,该函数把传给bind的第一个参数当做执行上下文,由于args已经是一个数组,排除第一项,将之后的部分作为第二部分参数传给apply。

*进阶版本的手写bind可以参考若川大神的文章:https://juejin.cn/post/6844903718089916429 *

本文使用 mdnice 排版