js 中 call、apply和bind的区别

js 中 call、apply和bind的区别

参考:https://zhuanlan.zhihu.com/p/82340026

call,apply,bind的作用是用来改变this的指向。

示例:

var name = "lucy";
let obj = {
    name: "martin",
    say: function () {
        console.log(this.name);
    }
};
obj.say(); //martin,this指向obj对象
setTimeout(obj.say, 0); //lucy,this指向window对象

可以观察到,正常情况下 say 方法中的 this 是指向调用它的 obj 对象的,而定时器 setTimeout 中的 say 方法中的 this 是指向 window 对象的(在浏览器中),这是因为 say 方法在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,但我们需要的是 say 方法中 this 指向 obj 对象,因此我们需要修改 this 的指向。

1. call

call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当第一个参数为nullundefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

示例:

var arr = [1,10,5,8,3];
console.log(Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])); //10

call以参数列表的形式传入,而apply以参数数组的形式传入。

2. apply

apply 接受两个参数,第一个参数是 this 的指向第二个参数数组,且当第一个参数为 null、undefined 的时候,默认指向 window (在浏览器中),使用 apply 方法改变 this 指向后原函数会立即执行,且此方法只是临时改变 this 指向一次

示例:

var name = "martin";
var obj = {
    name: "lucy",
    say: function(year, place){
        console.log(this.name + " is " + year + " born from "+place);
    }
};
var say = obj.say;
setTimeout(function(){
    say.apply(obj, ["1996", "China"])
} ,0); //lucy is 1996 born from China, this 改变指向了obj
say("1996", "China") //martin is 1996 born from China,this指向window,说明apply只是临时改变一次this指向

小技巧:改变参数传递方式

示例:

求数组中的最大值:

var arr = [1,10,5,8,3];
console.log(Math.max.apply(null, arr)); //10

其中Math.max函数的参数是以参数列表,如:Math.max(1,10,5,8,3)的形式传入的,因此我们没法直接把数组当做参数,但是apply方法可以将数组参数转换成列表参数传入,从而直接求数组的最大值。

3. bind

bind方法和call很相似,第一个参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

示例:

var arr = [1,10,5,8,12];
var max = Math.max.bind(null, arr[0], arr[1], arr[2], arr[3])
console.log(max(arr[4])); //12,分两次传参

可以看出,bind方法可以分多次传参,最后函数运行时会把所有参数连接起来一起放入函数运行。

4. 实现bind方法

4.1 简易版

Function.prototype.bind=function () {
    var _this = this;
    var context = arguments[0];
    var arg = [].slice.call(arguments, 1);
    return function(){
        arg = [].concat.apply(arg, arguments);
        _this.apply(context, arg);
    }
};

4.2 完美版

// 实现bind方法
Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
    	// closest thing possible to the ECMAScript 5
        // internal IsCallable function
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function() {},
        fBound = function() {
            // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
            return fToBind.apply(this instanceof fBound 
                                 ? this 
                                 : oThis,
         	// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    // 维护原型关系
    if (this.prototype) {
        // 当执行Function.prototype.bind()时, this为Function.prototype 
        // this.prototype(即Function.prototype.prototype)为undefined
        fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();
    return fBound;
};
var arr = [1,11,5,8,12];
var max = Math.max.bind(null,arr[0],arr[1],arr[2],arr[3]);
console.log(max(arr[4])); //12

5. 总结

  • 三者都可以改变函数的this对象指向。
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
  • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入。
  • bind 是返回绑定this之后的函数,便于稍后调用;applycall 则是立即执行 。
JavaScript 文章被收录于专栏

前端技术分享

全部评论

相关推荐

找不到工作死了算了:没事的,雨英,hr肯主动告知结果已经超越大部分hr了
点赞 评论 收藏
分享
10-13 17:47
门头沟学院 Java
wulala.god:图一那个善我面过,老板网上找的题库面的
点赞 评论 收藏
分享
评论
1
1
分享
牛客网
牛客企业服务