深入理解call、apply、bind(改变函数中的this指向)

在JavaScript中call、apply、bind是Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果。本文将对这三个方法进行详细的讲解,并列出几个经典应用场景。

区分:

1、call(object,arg1,arg2) ,call方法的第一个参数是函数中this重新指向的对象,剩下的参数是传入该函数的形参

不传,或者传null,undefined, 函数中的 this 指向 window 对象,传递另一个函数的函数名,函数中的 this 指向这个函数的引用,传递字符串、数值或布尔类型等基础类型,函数中的 this 指向其对应的包装对象,如 String、Number、Boolean,传递一个对象,函数中的 this 指向这个对象。

      function say(){
         console.log(this);
      }
      function eat(){
      }
      var obj={
         name:'Bob',
      };

      say.call();             //this指向window
      say.call(null);         //this指向window
      say.call(undefined);    //this指向window
      say.call(eat);          //this指向函数得引用 function eat(){}
      say.call(1);            //this指向数值基本类型得包装对象Number
      say.call("str");        //this指向数值基本类型得包装对象String
      say.call(true);         //this指向数值基本类型得包装对象Boolean
      say.call(obj);          //this指向这个对象obj

call的作用是允许在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,如下

      var a={
         name:'Bob',
         food:'fish',
         say:function(){
            console.log('HI,this is a.say!!');
         }
      }

      function b(name){                      //b.call(a,'Tom');使得a对象能调用其他函数方法
         console.log("post Params:"+name);   //a对象使用了b('Tom')方法, 输出post Params: Tom
         console.log('I am '+this.name);     //a对象获取了自己的属性 ,输出 I am Bob
         this.say();                         //a对象使用自己的方法, 输出 HI,this is a.say!!
      }

      b.call(a,'Tom');

2、apply(object,[arg1,arg2]),apply方法的第一个参数是函数中this重新指向的对象,第二个参数数组是传入该函数的形参;和call方法唯一区别是第二参数的传递形式是数组。使用如下

      function b(x,y,z){                      
        console.log(x,y,z);   //会将多个参数拼接输出                      
      }
      //apply() 方法接收的参数是数组形式,但是传递给调用的函数时是用参数列别的形式传递的
      b.apply(null,[1,2,3]);  //输出 1 2 3 ,这里等同于window对象调用b方法并使用参数[1,2,3]

3、bind(object,arg1,arg2) ,bind方法是ES5 新增的一个方法,传参和call方法一致。与call、apply方法的区别是,call和apply方法会对目标函数进行自动执行,会返回一个新的函数。call和apply无法在事件绑定函数中使用。而bind弥补了这个缺陷,在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题,

      var obj = {
         name: 'onepixel'
      };
      //给document添加click事件监听函数,并绑定onclick函数
      //通过bind方法设置onclick的this指向是obj,并传递参数p1,p2
      document.addEventListener('click',onClick.bind(obj,22,66),false);

      //可以理解为当网页触发click事件时,obj对象执行onclick函数并传递给该函数参数p1,p2
      function onClick(p1,p2){
         console.log(this.name,p1,p2);  //输出 onepixel 22 66
      }
      var button=document.getElementById("button"),
      text=document.getElementById("text");

      button.onclick=function(){  //声明按钮的点击事件触发的函数
         console.log(this.id);
      }.bind(text);     //改变this的指向

注意:一旦函数通过bind绑定了有效的this对象,那么在函数执行过程中this会指向该对象,即使使用call、apply也不能改变this的指向

bind 进行深入的理解,我们来看一下 bind 的 polyfill 实现(ie6~ie8不支持该方法):

if (!Function.prototype.bind) {  //浏览器js中不支持bind方法情况下
    Function.prototype.bind = function(context) {  //在函数原型对象自定义bind方法,context是this重指向的对象,也就是上面function(){}.bind(text)中的text对象
        var self = this  //因为这个自定义的bind方法返回的是一个匿名函数,匿名函数具有全局性,其this会指向window。所以这里需要将bind方法触发时的调用对象this进行保存
            , args = Array.prototype.slice.call(arguments);  //这里借用了数组原型对象的slice方法将形参 类数组转化为真正的数组
            
        return function() {  //bind方法返回一个匿名函数
            return self.apply(context, args.slice(1));    //self就是函数触发时的调用对象,this重指向bind方法的第一个参数对象,
        }
    };
}

代码解析:在浏览器不支持bind方法时,自定义bind方法中会返回一个匿名函数,该匿名函数保存bind方法调用时的this,这里是self=this; 并通过self.apply(newObj, args); 的原理实现改变this指向不执行函数的效果

使用场景:

1、继承

JavaScript中没有诸如Java、C# 等高级语言中的extend 关键字,因此JS 中没有继承的概念,如果一定要继承的话,call 和 apply 可以实现这个功能:

      function Animal(name,weight){
         this.name=name;
         this.weight=weight;
      }
      function Cat(){
         Animal.apply(this,['cat','10kg']);  //理解为在Cat函数对象创建时会使用Animal()方法
         //Animal.call(this,'cat','10kg');
         this.say=function(){
            console.log('I am '+this.name+' , my weight is '+this.weight);
         }
      }
      var cat = new Cat();
      cat.say();  //输出 I am cat , my weight is 10kg

2、移花接木

在讲下面的内容之前,我们首先来认识一下JavaScript 中的一个非标准专业术语:ArrayLike (类数组/伪数组)

ArrayLike 对象即拥有数组的一部分行为,在DOM 中早已表现出来,而jQuery 的崛起让ArrayLike 在JavaScript 中大放异彩。ArrayLike 对象的精妙在于它和JS 原生的 Array 类似,但是它是自由构建的,它来自开发者对JavaScript 对象的扩展,也就是说:对于它的原型(prototype)我们可以自由定义,而不会污染到JS原生的Array。 

ArrayLike 对象在JS中被广泛使用,比如DOM 中的NodeList, 函数中的arguments 都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call 将数组的某些方法`移接`到ArrayLike 对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:

      function test(){
         console.log(typeof(arguments));     //输出Object ,ArrayLike是类数组对象

         //检测arguments是否是Array的实例
         console.log(arguments instanceof Array);  //输出 false
         console.log(Array.isArray(arguments));    //输出 false

         //判断arguments是否有forEach的方法
         console.log(arguments.forEach);          //输出 undefined

         //将数组中的forEach方法应用到arguments上
         Array.prototype.forEach.call(arguments,
            function(item){console.log(item);  //输出 1 2 3 4 5
         });
      }
      test(1,2,3,4,5);

除此之外,对于apply 而言,我们上面提到了它独有的一个特性,即apply 接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply 看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而我们知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math 对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply 不仅可以将Math 对象的max 方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:

var arr = [-1,2, 3, 1, 5, 4, 223, 11];
var a=Math.max.apply(null, arr); // 223
console.log(a);

学习网址:https://blog.csdn.net/u014267183/article/details/52610600软谋前端 (深入理解 call,apply 和 bind

猜你喜欢

转载自blog.csdn.net/zhouzuoluo/article/details/84935106