改变This指向的三种方式及实现—call、apply、bind

改变This指向的三种方式及实现---call、apply、bind

前言

在上篇文章 可能是你看过最完整的this指向总结!中介绍了this指向在不同环境下的总结情况,里面提到过:

this 指向的值是可以通过手动方式去改变的,比如call、bind、apply方法。

那么在本篇中我们介绍的是call、bind、apply方法的使用以及如何实现自己的myCall、myBind、myApply方法。

一、call

1. 使用方法

function person(a,b,c){
  console.log(this);
  console.log(this.name,this.age)
}

person();  // 1.直接调用,this指向window
           // 打印  window
           // 打印  undefined,undefined
           
const obj = {name:"owl",age:18}
person.call(obj)                 // 2.传入指定的this值
                                 // 打印 {name: 'owl', age: 18}
                                 // owl 18


function person(a,b,c){
  console.log(a,b,c)
  console.log(this);
  console.log(this.name,this.age)
}
person.call(obj,1,2,3)          // 3.给person函数传入指定的this值和实参值
                                // 打印 1 2 3
                                // {name: 'owl', age: 18}
                                // owl 18

2. 实现myCall方法

介绍完call()方法以后,我们来尝试写一个自己的myCall方法,具体实现代码和注释如下:

Function.prototype.myCall = function (obj) {
        if (typeof this !== "function") {
                  throw new Error(
                    "Function.prototype.myCall - what is trying to be bound is not callable"
                  );
                }
        
        const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window
        ctx.func = this;           // 2.在ctx对象上新增一个属性func,并且给他赋值为this
                                   // this就是调用myCall函数的函数,在本例中就是person()方法
                                   
        const args = Array.from(arguments).slice(1); // 3.处理传入的参数,第一个参数为对象obj,
                                                     // 所以从第二个开始参数截取
        const result = arguments.length > 1 ? ctx.func(...args) : ctx.func(); 
        // 4. 如果传入参数,我们就把实参带入到func函数中执行,如果没有,则直接执行。
        delete ctx.func;   // 5. 执行完函数以后,记得删除掉这个“中间变量”属性 ctx
        return result;     // 6. 返回result
      };

在上面的代码中,func其实就是person函数,ctx则是我们传入要指定this指向的对象,也就是 {name: 'owl', age: 18}

那么我们在第四步使用ctx.func()或者ctx.func(...args)调用func()时是不是就满足了上篇文章中的调用对象的函数方法时,被调用函数中的this永远指向这个对象

所以自然而然就实现了我们手动改变this指向的目的。

  var obj = {
        name: "owllai",
      };

       function testCall(a, b, c) {
         console.log(this.name, a, b, c);
       }

    testCall.myCall(obj,1,2,3)

apply

  • apply和call的唯一区别就在于,接收的第二个参数为类数组

  • 除此之外,和call几乎一模一样,所以我们在使用和实现自定义apply方法的代码里只需要修改对应的部分就行了。

1.使用方法


function person(a,b,c){
  console.log(this);
  console.log(this.name,this.age)
}

person();  // 1.直接调用,this指向window
           // 打印  window
           // 打印  undefined,undefined
           
const obj = {name:"owl",age:18}
person.apply(obj)                 // 2.传入指定的this值
                                 // 打印 {name: 'owl', age: 18}
                                 // owl 18


function person(a,b,c){
  console.log(a,b,c)
  console.log(this);
  console.log(this.name,this.age)
}
person.apply(obj,[1,2,3])       // 3.给person函数传入指定的this值和实参值(类数组对象)
                                // 打印 1 2 3
                                // {name: 'owl', age: 18}
                                // owl 18


2.实现myApply方法

 Function.prototype.myApply = function (obj) {
         if (typeof this !== "function") {
              throw new Error(
                "Function.prototype.myApply - what is trying to be bound is not callable"
                          );
        }
        const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window
        ctx.func = this;           // 2.在ctx对象上新增一个属性func,并且给他赋值为this
                                   // this就是调用myApply函数的函数,在本例中就是person()方法
                                   
        const args = arguments[1]; // 3.处理传入的参数,第一个参数为对象obj,
                                   // 第二个参数为数组实参
        const result = arguments[1] ? ctx.func(...arguments[1]) : ctx.func(); 
        //第四步: 调用方法,获得结果。
        delete ctx.func;
        return result;
      };

myApply方法里面,我们只需要更改两点:

  • 第三步获取参数上,直接获取arguments数组的第二项
  • 第四步调用方法上,传入获取到的arguments 数组的第二项

bind

1.使用方法

  • bind接收的参数形式和call有点相似的:第一个参数是指定this指向的值,第二个参数开始则是执行函数需要的形参

  • bind方法在调用以后不会像call、apply一样直接执行,而是返回一个新函数。

语法:
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, arg2, /* …, */ argN)

 function person(a, b, c) {
        console.log(this);  
        console.log(this.name, this.age);
      }

      const obj = { name: "owl", age: 18 };
      let newPerson =  person.bind(obj); 
      console.log(newPerson); // ƒ person(a, b, c) {
                              //      console.log(this);
                              //      console.log(this.name, this.age);
                              //    }
      newPerson();            // {name: 'owl', age: 18}
                              // owl  18
                              
function person(a, b, c) {
        console.log(a,b,c)
        console.log(this);  
        console.log(this.name, this.age);
      }                              
                              
      let newPersonWithArgs = person.bind(obj,1,2,3) ;
      newPersonWithArgs();    // 1 2 3 
                              // {name: 'owl', age: 18}
                              // owl  18


2.实现myBind方法

简单版本

      function person(a, b, c) {
        console.log(a, b, c);
        console.log(this);
        console.log(this.name, this.age);
      }
      let obj = {
        name: "owllai",
        age: 18,
      };

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

        var self = this; // 1. 这个this代表调用myBind方法的函数,在本例中也就是person
        var args = Array.prototype.slice.call(arguments, 1); 
        // 2. 获取传入的其他参数,从arguments数组的第二项开始截取
        
        var fn = function () {        // 3.定义一个要返回的函数   
          //4. 返回的新函数也是可以接收参数的,所以我们要一起获取                         
          var newArgs = Array.prototype.slice.call(arguments);
          //5. 将obj和参数一起传入,使用apply来执行
          return self.apply(obj, args.concat(newArgs));  
        };
        //6. 最后返回结果函数
        return fn;
      };

      let newBind = person.myBind(obj, 1, 2, 3);
      newBind();


myBind的简单版本已经实现,但是还有一个问题没有解决,那就是既然返回的是一个新函数,那除了直接调用newBind() 方法以外,还可以将newBind当成构造函数,使用 new 关键字进行实例化。

比如下面的实例化例子:

let newBind = person.myBind(obj, 1, 2, 3);
let n = new newBind();

  1. 我们知道变量n正常来说应该是newBind函数的实例化对象,构造函数的this指向实例化对象。

  2. 而在return self.apply(obj, args.concat(newArgs)); 这一行代码中,我们是写死this指向为obj。这样显然是不对的。

  3. 我们必须考虑到new的情况,所以对简单版本的myBind代码进行改造

完整版本


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

        var self = this; // 这个this代表调用myBind方法的函数,在本例中也就是person函数
        var args = Array.prototype.slice.call(arguments, 1);
        var fn = function () {
          var newArgs = Array.prototype.slice.call(arguments);
          return self.apply(
           //如果没有进行判断,永远写死obj作为apply的第一个参数,那么如果对fn这个返回函数进行new时,这个fn函数的this指向永远是外部传过来的obj
           //这样是不正确的,如果作为new关键字使用这个fn函数,this指向必须是指向new出来的实例对象
           //怎么判断是不是用new关键字来调用呢?
           // 我们可以用 instanceof 来判断返回函数的原型是否在实例的原型链上
           // 如果返回函数是被new了,那这个返回函数的实例对象的this就指向了person函数
           this instanceof fn ? this : obj,
            args.concat(newArgs)
          );
 
        };

        // 创建一个空函数
        var tmpFn = function () {};

        // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
        // 可以直接使用  fn.prototype = this.prototype  (this代表fn)
        // fn.prototype = this.prototype;  
        // 也就是让返回函数的原型对象和person函数的原型对象映射
        // 至于为什么使用一个空函数 tmpFn 作为中介,把 fn.prototype 赋值为空对象的实例(原型式继承),
        // 这是因为直接 fn.prototype = this.prototype 有一个缺点,修改 fn.prototype 的时候,也会直接修改 this.prototype ;
        
        // tmpFn空函数的原型指向绑定函数的原型
         tmpFn.prototype = this.prototype; //(this代表person函数)

        // 空对象的实例赋值给 fn.prototype
        fn.prototype = new tmpFn();
        return fn;
      };

原文链接:https://juejin.cn/post/7311997169950130227 作者:秋天的一阵风

(0)
上一篇 2023年12月14日 上午10:11
下一篇 2023年12月14日 上午10:22

相关推荐

发表回复

登录后才能评论