前端必须知道的call,apply,bind

234 阅读3分钟

为什么说作为一个前端必须要知道并且理解call 和apply,bind的,因为向我们日常用的很多库,vuejs,react,lodash,momnetjs里面实现的方法很多都会用到,所以作为一个前端,如果想深入学习一些框架源码,必须要对call,apply,bind了如指掌。

一. Function.prototype.call()

基本概念:

call() : 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。并执行这个函数。


语法:function.call(thisArg, arg1, arg2, ...),后面arg1,arg2为参数列表:

示例一:


  •  执行b():因为在全局调用了这个函数。this是指向window的,所以this.name输出为: "鬼泣",
  • 执行obj1.func()func内部的this是指向obj1,  所以this.name输出为:"剑魂"
  • 执行obj1.func.call:当我们使用call的时候,并把对象A1作为第一个参数传递过去的时候,此时的this指向了A1,所以this.name输出为:"阿修罗",

      let name = '鬼泣';
      let A1 = { name: '阿修罗' };
      let obj1 = { 
            name: '剑魂',
            func: function (name, level) {          
                console.log(this.name);
                console.log(name);
                console.log(level);
                    }
                };      l
            let b = obj1.func;
            b();      
            obj1.func();
            obj1.func.call(a2, '狂战士', 1000);


示例一:在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,从而不需要自己手动的一个一个去添加同一个属性:

      function Profession(name, level) {    
        this.name = name;
        this.level = level;
      }
      
      function Role(name, level) { 
        Profession.call(this, name, level);
      }
     
      let createRole = new Role("剑魂", 95);
      console.log(createRole.level); // 95


模拟一个call():

      Function.prototype.call2 = function (context) {
          var context = context || window;    // 如果没有传递参数,context是指向window
          context.fn = this; // 此时this为调用者函数
          var args = []; // 获取传进来的参数

          for (var i = 1, len = arguments.length; i < len; i++) {
              args.push("arguments[" + i + "]");
          } 

        // 执行调用这函数函数,        
        var result = eval("context.fn(" + args + ")");        
        delete context.fn;        // 并且把函数执行完后的返回执行结果        
        return result;
       };



二. Function.prototype.apply()

基本概念:

方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

语法:function.call(thisArg, [params1, params2, ...])


示例1:其实apply 很 call 的功能差不多。只是参数传递方式不一样:第一个参数都是this的指向。不同点在于:

  • call的后面参数传递是以参数列表的方式,
  • apply的后面参数传递是以数组的方式,

引用上面的例子:

      let name = "鬼泣";
      let A1 = { name: "阿修罗" };
      let obj1 = {
        name: "剑魂",
        func: function (name, level) {
          console.log(this.name);
          console.log(name);
          console.log(level);
           },      
        };      
        let b = obj1.func;
        b();
        obj1.func();
        obj1.func.apply(a2, ["狂战士", 1000]); // 把call改为apply的方式,执行的结果是一样的


模拟一个apply():

Function.prototype.apply22 = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}



三. Function.prototype.bind()

基本概念:

bind()方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

      name = "狂战士";
      var comp = {        
        name: "剑魂", 
        getName: function (level) {
          return this.name;
            },
      };      
    comp.getName(); 
    // "剑魂"      
    var comp11 = comp.getName;      
    comp11(); // "狂战士"      var comp22 = comp11.bind(comp, 85); // bind创建了一个新的函数      comp22(); // "剑魂"

这里有一个小坑:

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

示例2:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');

虽然在创建创建对象的时候bind传递的参数为foo,但是在new 的时候,bar内部的this是指向obj的,所以this.value输出为undefind



模拟bind:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}