手写bind、call、apply - 图1

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

this指向

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

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

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

  • 再来一个:
  1. var name = "如来佛祖";
  2. var a = {
  3. name: "孙悟空",
  4. fn: function() {
  5. name: "猪八戒",
  6. console.log(this.name); //孙悟空
  7. }
  8. };
  9. window.a.fn();

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

改变this 的指向

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

使用 ES6 的箭头函数

  1. var name = "如来佛祖";
  2. var a = {
  3. name: "孙悟空",
  4. func1: function() {
  5. console.log(this.name);
  6. },
  7. func2: function() {
  8. setTimeout(() => {
  9. this.func1();
  10. }, 100);
  11. }
  12. };
  13. a.func2(); //孙悟空

在函数内部使用 _this = this

  1. var name = "如来佛祖";
  2. var a = {
  3. name: "孙悟空",
  4. func1: function() {
  5. console.log(this.name);
  6. },
  7. func2: function() {
  8. var _this = this;
  9. setTimeout(function() {
  10. _this.func1();
  11. }, 100);
  12. }
  13. };
  14. a.func2(); //孙悟空

使用 apply、call、bind

  1. 使用 apply
  2. var a = {
  3. name: "孙悟空",
  4. func1: function() {
  5. console.log(this.name);
  6. },
  7. func2: function() {
  8. setTimeout(
  9. function() {
  10. this.func1();
  11. }.apply(a),
  12. 100
  13. );
  14. }
  15. };
  16. a.func2();
  1. 使用 call
  2. var a = {
  3. name: "孙悟空",
  4. func1: function() {
  5. console.log(this.name);
  6. },
  7. func2: function() {
  8. setTimeout(
  9. function() {
  10. this.func1();
  11. }.apply(a),
  12. 100
  13. );
  14. }
  15. };
  16. a.func2(); //孙悟空
  1. 使用bind
  2. var a = {
  3. name: "孙悟空",
  4. func1: function() {
  5. console.log(this.name);
  6. },
  7. func2: function() {
  8. setTimeout(
  9. function() {
  10. this.func1();
  11. }.bind(a)(),
  12. 100
  13. );
  14. }
  15. };
  16. a.func2(); //孙悟空

call、apply、bind的区别

  • call语法
    fun.call(thisArg[, arg1[, arg2[, …]]])
  • apply语法
    fun.apply(thisArg, [argsArray])
  • bind语法
    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

补充:

apply,call,bind三者的区别

  • 三者都可以改变函数的this对象指向。
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
  • 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
  • bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行

bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。

小技巧:改变参数传入方式
示例:
求数组中的最大值:

  1. var arr=[1,10,5,8,3]; console.log(Math.max.apply(null, arr)); //10

其中Math.max函数的参数是以参数列表,如:Math.max(1,10,5,8,3)的形式传入的,因此我们没法直接把数组当做参数,但是apply方法可以将数组参数转换成列表参数传入,从而直接求数组的最大值。

call方法

call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
示例:

  1. var arr=[1,10,5,8,3]; console.log(Math.max.call(null,arr[0],arr[1],arr[2],arr[3],arr[4])); //10

采纳以参数列表的形式传入,而apply以参数数组的形式传入。

bind方法

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
示例:

  1. var arr=[1,10,5,8,12]; var max=Math.max.bind(null,arr[0],arr[1],arr[2],arr[3]) console.log(max(arr[4])); //12,分两次传参

手写call、apply、bind

以下内容部分来自来自 Medium

  • 手写call
  1. Function.prototype.myOwnCall = function(someOtherThis) {
  2. someOtherThis = someOtherThis || global;
  3. var uniqueID = "00" + Math.random();
  4. while (someOtherThis.hasOwnProperty(uniqueID)) {
  5. uniqueID = "00" + Math.random();
  6. }
  7. someOtherThis[uniqueID] = this;
  8. const args = [];
  9. // arguments are saved in strings, using args
  10. for (var i = 1, len = arguments.length; i < len; i++) {
  11. args.push("arguments[" + i + "]");
  12. }
  13. // strings are reparsed into statements in the eval method
  14. // Here args automatically calls the Array.toString() method.
  15. var result = eval("someOtherThis[uniqueID](" + args + ")");
  16. delete someOtherThis[uniqueID];
  17. return result;
  18. };
  • 手写apply
  1. Function.prototype.myOwnApply = function(someOtherThis, arr) {
  2. someOtherThis = someOtherThis || global;
  3. var uniqueID = "00" + Math.random();
  4. while (someOtherThis.hasOwnProperty(uniqueID)) {
  5. uniqueID = "00" + Math.random();
  6. }
  7. someOtherThis[uniqueID] = this;
  8. var args = [];
  9. var result = null;
  10. if (!arr) {
  11. result = someOtherThis[uniqueID]();
  12. } else {
  13. for (let i = 1, len = arr.length; i < len; i++) {
  14. args.push("arr[" + i + "]");
  15. }
  16. result = eval("someOtherThis[uniqueID](" + args + ")");
  17. }
  18. delete someOtherThis[uniqueID];
  19. return result;
  20. };
  • 手写bind
  1. // 模拟 bind
  2. Function.prototype.bind1 = function() {
  3. //arguments获取一个函数所有的参数,它是一个列表
  4. // 将参数拆解为数组
  5. const args = Array.prototype.slice.call(arguments);
  6. // 获取 this(数组第一项)
  7. const t = args.shift();
  8. // 好比fn1.bind(...) 中的 fn1 或者下面的_this=this
  9. // const self = this;
  10. _this=this
  11. // 返回一个函数(bind本来是返回一个函数)
  12. return function() {
  13. // apply的第一个参数就是this
  14. return _this.apply(t, args);
  15. };
  16. };

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