浅析call,apply和bind

323 阅读4分钟

bind,call和apply简单使用及其区别

三者都可以改变this的指向,尤其call和apply的功能及其类似,下面按照我自己的理解对其进行一些区分

apply

从简单的开始,先说说apply吧!

apply()方法中传入一个指定的this值和一个指定的数组,数组中的元素作为调用apply()方法的原函数的参数,this可以为null,这时将默认指向window

先上一个例子,比较好理解

var foo = {
	value: 1
};

function bar(name, age) {
	console.log(name)
	console.log(age)
	console.log(this.value);
}

bar.apply(foo, ['kevin', 18]);
// kevin
// 18
// 1

值得注意的事情是,当使用apply()方法时,控制台会打印出我们所希望打印的内容,也就是bar函数执行了,在改变其this指向后,执行改变后的函数,关于这一点,call()方法的执行也是一样的。

显然我们传入的数组,会被正确解析为bar中所需要的参数

call

现在我们来说一下与apply极为类似的call,先说一下区别,call()方法的调用和apply只有一个区别,就是传入的参数不同,apply第二个参数是一个数组,但call方法后面直接跟着需要传入原函数的参数。

调用call的方式如下

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1

百思不得其解,有了call为什么还要apply呢,经过一番查阅后,了解到使用call的速度比apply快,那疑惑就更严重了,apply还有存在的意义吗?我们不妨假设当需要传入的参数很多时,一般都是存放在数组中,如果只有call的话,就需要我们对数组做一些处理,转换成参数列表的形式,但是使用apply的话就减轻了工作量,可以这样认为吧,apply时call的语法糖。(为什么参数多的时候会存放在数组里面捏?本孩认为,数组可以被认为一个整体,不易出现丢参的情况,而且数组有长度,可以轻而易举的知道到底有多少个参数)

bind

终于到bind了,但其实不难,别害怕 同样,还是那个栗子,你不会已经吃了吧,不会吧,不会吧!!!

var foo = {
	value: 1
};

function bar(name, age) {
	console.log(name)
	console.log(age)
	console.log(this.value);
}

bar.bind(foo, 'kevin', 18);
// 没有任何输出

为什么会没有输出呢?上文已经提到了,这就是和bind、call的区别了

bind不会执行bar函数,只是改变了其this指向,并且给它传入了参数,添加一行代码呢?如下:

var foo = {
	value: 1
};

function bar(name, age) {
	console.log(name)
	console.log(age)
	console.log(this.value);
}

bar.bind(foo, 'kevin', 18);
bar();
// undefined
// undefined
// undefined

对的,你没看错,输出了三个undefined,因为虽然调用了bar(),但this指向了window且没有传入任何参数,理所当然都是undefined了,可见使用了bind后并不会改变原函数,其实call和apply也都不会改变原参数,也就是说call和apply是一次性的,这里为什么我没说bind呢,因为他会返回一个函数,如果我们保存这个函数,它就不是一次性的了,如果再执行这个函数就可以达到我们想要的效果了,注意:执行apply和call时,就如同执行原函数,返回值和原函数相同,如果原函数不返回值,则apply和call也不会返回任何值!

可以像下面这样使用bind

var foo = {
	value: 1
};

function bar(name, age) {
	console.log(name)
	console.log(age)
	console.log(this.value);
}

var bar2 = bar.bind(foo, 'kevin', 18);
bar2();
// kevin
// 18
// 1

手写简单bind,call和apply

这部分难度上升了,感谢能看到这里,我也要加油继续肝下面的内容

实现简单的call

Function.prototype.call2 = function (context) {// 此时的context为传入的第一个参数,也就是我们希望this所指向的对象
    var context = context || window; // 输入null则指向window
    context.fn = this; // 给传入的对象创建一个fn属性,值为this,this为原函数,也就是调用call2的函数

    // 实现传递参数
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');// 会自动调用args.toSting(),比如["arguments[1]","arguments[2]"]会转化成 arguments[1], arguments[2]

    // 删除为其添加的属性
    delete context.fn
    return result;
}

实现简单的apply

Function.prototype.apply2 = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

实现简单的bind

见我上一篇博客初探bind

更多

如果有什么受启发的地方,那一定是你的理解力太好了,我觉得我有点啰嗦

还有想知道手写上述函数的细节请参考大神系列文章

推介阅读文章前辈的理解

努力coding

觉得不错就点赞夸一下本孩吧