自学内容网 自学内容网

【JavaScript】call、apply、bind

1、概述

在JavaScript 中,call、apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向。

2、call

call([thisObj[,arg1[, arg2[, [,.argN]]]]])

调用一个对象的一个方法,以另一个对象替换当前对象。

call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

参数说明:

  • thisObj:改变调用call方法的函数内的this,此到达函数内部后会自动转成对象形式变成this
  • arg1…:对应调用call方法的函数的第一个参数及之后。

thisObj 的取值:

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

应用场景:

  • js的函数上可以使用call方法来改变函数中的this指向

    function a(){   
      console.log(this);   // 输出函数a中的 this 对象
    }       
    
    function b(){}       // 定义函数 b
    
    var c = {name:"call"};// 定义对象 c  
    
    a.call();   // window
    a.call(null);   // window
    a.call(undefined);   // window
    a.call(1);   // Number
    a.call('');   // String
    a.call(true);   // Boolean
    a.call(b);   // function b(){}
    a.call(c);   // Object
    
  • 通过call实现两个函数之间的继承

    function Animal(name,weight){
       this.name = name;
       this.weight = weight;
    }
    
    function Cat(){
        Animal.call(this,'cat','50');
      //Animal.apply(this,['cat','50']);
    
       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 50
    
    function class1(){   
      this.name = function(){   
        console.log("method - class1");   
      } 
    }   
    function class2(){ 
      class1.call(this);  //此行代码执行后,当前的this指向了class1(也可以说class2继承了class1)   
    }   
    
    var f = new class2();   
    f.name();   //调用的是class1内的方法,将class1的name方法交给class2使用
    
  • 通过call实现两个函数之间的替换

    function eat(x,y){   
      console.log(x+y);   
    }   
    function drink(x,y){   
      console.log(x-y);   
    }   
    eat.call(drink,3,2);
    
    // 输出:5
    // 这个例子中的意思就是用 eat 来替换 drink,eat.call(drink,3,2) == eat(3,2) ,所以运行结果为:console.log(5);
    // 注意:js 中的函数其实是对象,函数名是对 Function 对象的引用。
    
  • 通过call实现两个函数之间的方法和属性的继承

    function Animal(){   
      this.name="animal";   
      this.showName=function(){   
        console.log(this.name);   
      }   
    }   
    function Dog(){   
      this.name="dog";   
    }   
    var animal=new Animal();   
    var dog=new Dog();       
    
    animal.showName.call(dog);
    
    //输出:dog
    //在上面的代码中,我们可以看到Dog里并没有showName方法,那为什么(this.name)的值是dog呢?
    
    //关键就在于最后一段代码(animal.showName.call(dog)),意思是把animal的方法放到dog上执行,也可以说,把animal 的showName()方法放到 dog上来执行,所以this.name 应该是 dog。
    
    //继承
    function Animal(name){   
      this.name=name;   
      this.showName=function(){   
        console.log(this.name);   
      }   
    }   
    function Dog(name){   
      Animal.call(this,name);   
    }   
    var dog=new Dog("Crazy dog");   
    dog.showName();
    
    //输出:Crazy dog
    //Animal.call(this) 的意思就是使用 Animal对象代替this对象,那么Dog就能直接调用Animal的所有属性和方法。
    

3、apply

apply(thisArgs [,args[]])

apply 和 call 的唯一区别是第二个参数的传递方式不同,apply 的第二个参数必须是一个数组(或者类数组),而 call 允许传递一个参数列表。

虽然 apply 接收的是一个参数数组,但在传递给调用函数时,却是以参数列表的形式传递。

function b(x,y,z){
    console.log(x,y,z);
}
 
b.apply(null,[1,2,3]); // 1 2 3

4、bind

bind(thisArgs [,args...])

bind是ES5 新增的一个方法,它的传参和call类似,但又和 call/apply 有着显著的不同,即调用 call 或 apply 都会自动执行对应的函数,而 bind 不会执行对应的函数,只是返回了对函数的引用。

ES5引入 bind 的真正目的是为了弥补 call/apply 的不足,由于 call/apply 会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS 内部自动执行的。而 bind 在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题。

var obj = {name:'onepixel'};
 
/**
 * 给document添加click事件监听,并绑定onClick函数
 * 通过bind方法设置onClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',onClick.bind(obj,'p1','p2'),false);
 
//当点击网页时触发并执行
function onClick(a,b){
    console.log(
            this.name, //onepixel
            a, //p1
            b  //p2
    )
}

当点击网页时,onClick 被触发执行,输出onepixel p1 p2, 说明 onClick 中的 this 被 bind 改变成了obj 对象,为了对 bind 进行深入的理解,我们来看一下 bind 的 polyfill 实现:

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        var args = Array.prototype.slice.call(arguments, 1);
        var fToBind = this;  // this在这里指向的是目标函数
        var fBind = function () {
            return fToBind.apply(
                // 如果外部执行var obj = new fBind(), 则将obj作为最终的this,放弃使用oThis
                this instanceof fBind
                    ? this  // 此时的 this 就是 new 出的 obj
                    : oThis || this, // 如果传递的 oThis 无效,就将 fBind 的调用者作为this
                // 将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
                args.concat(Array.prototype.slice.call(arguments))
            );
        };
 
        // 将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
        fBind.prototype = this.prototype;
 
        // 返回fBind的引用,由外部按需调用
        return fBind;
    };
} 

一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。

var obj1 = {
    name: 'Tom'
}
var obj2 = {
    name: 'Joy' 
}
setTimeout(function() {
    console.log(this.name);
}.bind(obj1).bind(obj2), 0);
 
// Tom

当对一个函数调用多次bind的时候,最终起作用的是第一个bind,这是因为每个bind 都会返回一个闭包 fBind,fBind 里保存了执行目标函数的 scope 和 arguments。执行多次 bind ,目标函数会被多个闭包包装,然后从外到里去执行,当执行真正目标函数的时候,apply 函数中的 scope 和 arguments 读取当前闭包里的变量。

// 实现数组的去重功能
Array.prototype.unique = function(fn) {
        var rst = [];
        var tmp = {};
        this.forEach(function(val) {
            // 使用call来改变fn的this指向,这里传window
            var key = 'uniq' + (typeof fn === 'function' ? fn.call(window, val) : val);
            if (!tmp.hasOwnProperty(key)) {
                rst.push(val);
                tmp[key] = null;
            }
        }, this);
 
        return rst;
}
 
// 对象数组去重
var arr = [
    { id: 2 }, { id: 4 }, { id: 3 }, { id: 3 }, { id: 4 }, { id: 6 }
]
arr.unique(function(v) {
     console.log(this) // 使用bind传递了Array,则this一定是Array,而不会是window
     return v.id
}.bind(Array));

10、资料


原文地址:https://blog.csdn.net/weixin_42364929/article/details/143837095

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!