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 文章被收录于专栏

前端技术分享

全部评论

相关推荐

牛客154160166号:9月底还给我发短信,好奇怪,我24届的
点赞 评论 收藏
分享
沉淀一会:1.同学你面试评价不错,概率很大,请耐心等待; 2.你的排名比较靠前,不要担心,耐心等待; 3.问题不大,正在审批,不要着急签其他公司,等等我们! 4.预计9月中下旬,安心过节; 5.下周会有结果,请耐心等待下; 6.可能国庆节前后,一有结果我马上通知你; 7.预计10月中旬,再坚持一下; 8.正在走流程,就这两天了; 9.同学,结果我也不知道,你如果查到了也告诉我一声; 10.同学你出线不明朗,建议签其他公司保底! 11.同学你找了哪些公司,我也在找工作。
点赞 评论 收藏
分享
1 1 评论
分享
牛客网
牛客企业服务