call, apply, bind方法详解

本文深入探讨了JavaScript中改变函数this指向的三种方法:call(), apply()和bind()。通过实例解析了它们的用法及区别,特别是bind()生成新函数的特性。同时,提供了这些方法的自定义实现,帮助读者更深入理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在js中,call(),apply()和bind()是function自带的三个函数,这三个函数的作用都是用来改变this指向的。接下来,我们就详细理解一下这几个函数的用法。

共同点和区别

call(),appy(),bind()这几个方法的共同点就是改变this指向。

call()和apply()的用法:
看例子

function A(x, y){
            console.log(x,y);
            console.log(this);
            console.log(arguments);
        }
var C = {};
A.call(C,5,6);     //5 6  {}  arguments = [5,6]
A.apply(C,[5,6]);   //5 6  {}  arguments = [5,6]

从例子也可以看出,原来A函数的this指向是全局,但是通过call()和apply()调用之后,this指向是空对象C。
call()和apply()的用法只有一点不同,就是传参列表不同,其它完全相同。
再看例子

function person(age, male){
            this.age = age;
            this.male = male;
        }
var person1 = {};
person.call(person1,20, "female");   //person1 = {age: 20, male: "female"}
person.apply(person1,[20,"female"]);  //person1 = {age: 20, male: "female"}

从返回结果看,person1空对象通过调用call()和apply()函数为自己添加了age和male属性。

var person = {
            age: 20,
            male: "female",
            sayAge: function(){
                console.log("my age is"+this.age+" my male is "+this.male);
            }
        };

        var person1 = {
            age: 30,
            male: "male"
        };
        person.sayAge.call(person1);   //my age is30 my male is male
        person.sayAge.apply(person1);  //my age is30 my male is male

再来看bind()的用法,上面讲的无论是call()还是apply()都是立马就调用了对应的函数,但是bind()不会,bind()会生成一个新的函数并返回这个新函数,在以后想什么调用这个新函数都可以,bind()的传参形式和call()一致,第一个参数也是会绑定this的值,后面接受传递给函数的不定参数。
接下来看代码

var person = {
            age: 20
        };
var age = 30;
  function sayAge (male){
      var age = 50;
      console.log(this.age + " "+male);
  }
  sayAge("male");    //30 male   函数中的this指向window
  var foo = sayAge.bind(person,"female");    //20 female
  sayAge.call(person,"female");      //20 female
  sayAge.apply(person,["female"]);
  foo();                              //20 female

方法的实现

bind()方法的实现:
bind()方法有两条准则:
1.会返回一个函数
2.可以传参,既可以给bind()函数传参,又可以给返回函数传参。

首先满足第一个条件

Function.prototype.myBind = function(contex){
   var self = this;               //谁调用该函数this就指向谁
      return function(){
          self.apply(contex);
      }
  }

然后满足参数条件

Function.prototype.myBind = function(contex){
            var self = this;    //谁调用这个函数this就指向谁
            var args = Array.prototype.slice.call(arguments,1);    //去掉第一个参数,第一个参数表示是contex
            //类数组转换成了数组
            return function(){            //参数是两个函数传参的拼接
                var arguments = Array.prototype.slice.call(arguments);
                var newArgs = args.concat(arguments); 
                self.apply(contex,newArgs);
            };
        };

另外,返回函数还可以当构造函数使用,因此,还需要加入当构造函数使用时的程序:

Function.prototype.myBind = function(contex){
            var self = this;    //这里的this指向绑定函数
            var args = Array.prototype.slice.call(arguments,1);    //去掉第一个参数,第一个参数表示是contex
            //类数组转换成了数组
            var fbound = function(){            //参数是两个函数传参的拼接
                var arguments = Array.prototype.slice.call(arguments);
                var newArgs = args.concat(arguments);
                //当作为构造函数时,this指向实例,self指向绑定的函数。
                //当作为普通函数时,this指向window,self指向绑定函数。
                self.apply(this instanceof self ? this : contex, newArgs);
            };
            fbound.prototype = this.prototype;   //修改返回函数的prototype为绑定函数的prototype,实例就可以继承函数原型中的值,至此就形成了继承的关系。所以,当作为构造函数使用时,this指向实例,self指向绑定函数,结果返回true;相反,如果没有new的话,表示返回函数只是普通函数调用,这时,this指向window,self指向绑定函数,所以,返回false.
            return fbound;
        };

用call()方法实现bind();

Function.prototype.myBind = function(contex){
            var self = this;   
            var args = Array.prototype.slice.call(arguments,1);
            var fbound = function(){          
                var arguments = Array.prototype.slice.call(arguments);
                var newArgs = args.concat(arguments);
                self.call(this instanceof self ? this : contex, ...newArgs);
            };
            fbound.prototype = this.prototype;   
            return fbound;
        };

call()方法的实现:
基本实现过程分为三步:

  1. 为对象添加方法。
  2. 方法执行。
  3. 删除方法。
    程序:
Function.prototype.myCall = function(context){
            context.fn = this;    //这个this指向调用的函数,为对象添加一个函数
            //为这个函数传参
            var args = Array.prototype.slice.call(arguments,1);  //将类数组转化为了数组,且不包含第一个参数
            console.log(args);
            eval( 'context.fn('+ args + ')');    //字符串拼接成一个语句
            delete context.fn;
        };

还可以用es6的方法:

Function.prototype.myCall = function(context){
            context.fn = this;    //这个this指向调用的函数,为对象添加一个函数
            //为这个函数传参
            var args = Array.prototype.slice.call(arguments,1);  //将类数组转化为了数组,且不包含第一个参数
            console.log(args instanceof Array);
            context.fn( ...args );   //...扩展运算符将数组转化为用逗号分隔的序列
            delete context.fn;
        };

        Person.myCall(bar,"wang",18);

apply的实现

Function.prototype.myApply = function(context){
            context.fn = this;    //这个this指向调用的函数,为对象添加一个函数
            //为这个函数传参
            var args = [];
            for (var i = 0; i < arguments[1].length; i++){
                args[i] = arguments[1][i];
            }

            context.fn( ...args );
            delete context.fn;
        };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值