手写call、apply、bind

2,142 阅读3分钟

说到call、apply、bind首先是改变this指向的问题(ES5中),其次它们之间的区别,最后光说不练假把式,手写call、apply、bind!

this指向

在 ES5 中,其实 this 的指向,始终坚持一个原理: this 永远指向最后调用它的那个对象(也就是说:this取什么值是在函数执行时确定的,不是在函数定义时确定的)

  • 看个例子:
var name = "如来佛祖";
function a() {
  var name = "孙悟空";
  console.log(this.name); // 如来佛祖
  console.log("inner:" + this); //inner:[object Window]
}
a();
console.log("outer:" + this); //outer:[object Window]

很简单,像上面说的 this取什么值是在函数执行时确定的,最后k看调用 a 的地方 a(),前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a()这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined。

  • 再来一个:
var name = "如来佛祖";
var a = {
  name: "孙悟空",
  fn: function() {
    name: "猪八戒",
    console.log(this.name); //孙悟空
  }
};
window.a.fn();

为什么,这里会是孙悟空,而不是猪八戒呢,或者为什么不是如来佛祖呢,因为如上面的那句话:this取什么值是在函数执行时确定的,因为,window.a.fn()就表示,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,所以name是孙悟空。

改变this 的指向

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 apply、call、bind

使用 ES6 的箭头函数

    var name = "如来佛祖";
    var a = {
      name: "孙悟空",
      func1: function() {
        console.log(this.name);
      },
      func2: function() {
        setTimeout(() => {
          this.func1();
        }, 100);
      }
    };
    a.func2(); //孙悟空

在函数内部使用 _this = this

    var name = "如来佛祖";
    var a = {
      name: "孙悟空",
      func1: function() {
        console.log(this.name);
      },
      func2: function() {
        var _this = this;
        setTimeout(function() {
          _this.func1();
        }, 100);
      }
    };
    a.func2(); //孙悟空

使用 apply、call、bind

使用 apply
    var a = {
        name: "孙悟空",
        func1: function() {
          console.log(this.name);
        },
        func2: function() {
          setTimeout(
            function() {
              this.func1();
            }.apply(a),
            100
          );
        }
      };
    a.func2(); 

使用 call 
    var a = {
        name: "孙悟空",
        func1: function() {
          console.log(this.name);
        },
        func2: function() {
          setTimeout(
            function() {
              this.func1();
            }.apply(a),
            100
          );
        }
      };
    a.func2(); //孙悟空

使用bind 
    var a = {
        name: "孙悟空",
        func1: function() {
          console.log(this.name);
        },
        func2: function() {
          setTimeout(
            function() {
              this.func1();
            }.bind(a)(),
            100
          );
        }
      };
    a.func2(); //孙悟空

call、apply、bind的区别

  • call语法

    fun.call(thisArg[, arg1[, arg2[, ...]]])

  • apply语法

    fun.apply(thisArg, [argsArray])

  • bind语法

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

手写call、apply、bind

以下内容部分来自来自 Medium

  • 手写call
Function.prototype.myOwnCall = function(someOtherThis) {
    someOtherThis = someOtherThis || global;
    var uniqueID = "00" + Math.random();
    while (someOtherThis.hasOwnProperty(uniqueID)) {
      uniqueID = "00" + Math.random();
    }
    someOtherThis[uniqueID] = this;
    const args = [];
    
    // arguments are saved in strings, using args
    for (var i = 1, len = arguments.length; i < len; i++) {
      args.push("arguments[" + i + "]");
    }

    // strings are reparsed into statements in the eval method
    // Here args automatically calls the Array.toString() method.
    var result = eval("someOtherThis[uniqueID](" + args + ")");
    delete someOtherThis[uniqueID];
    return result;
  };


  • 手写apply
Function.prototype.myOwnApply = function(someOtherThis, arr) {
  someOtherThis = someOtherThis || global;
  var uniqueID = "00" + Math.random();
  while (someOtherThis.hasOwnProperty(uniqueID)) {
    uniqueID = "00" + Math.random();
  }
  someOtherThis[uniqueID] = this;

  var args = [];
  var result = null;
  if (!arr) {
    result = someOtherThis[uniqueID]();
  } else {
    for (let i = 1, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("someOtherThis[uniqueID](" + args + ")");
  }

  delete someOtherThis[uniqueID];
  return result;
};
  • 手写bind
// 模拟 bind
  Function.prototype.bind1 = function() {
    //arguments获取一个函数所有的参数,它是一个列表
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments);

    // 获取 this(数组第一项)
    const t = args.shift();

    // 好比fn1.bind(...) 中的 fn1 或者下面的_this=this
    // const self = this;
    _this=this
    
    // 返回一个函数(bind本来是返回一个函数)
    return function() {
      // apply的第一个参数就是this
      return _this.apply(t, args);
    };
  };

待更新❤️,后续继续补充