JavaScript中改变this指向的三种方式总结

作者:青柠

时间:2026-10-31

浏览:254

本文详细解析JavaScript中改变this指向的三种核心方法:call、apply和bind。通过对比它们的使用场景与参数差异,深入剖析其底层实现原理,并逐步演示如何手动实现myCall、myApply以及处理new关键字的完整版myBind,帮助开发者彻底掌握函数绑定机制。

文章目录

前言

在之前的文章《可能是你看过最完整的this指向总结!》中,我们探讨了this在不同执行环境下的指向规律,其中特别指出:

this的绑定值并非固定不变,而是可以通过特定手段进行手动调整,常用的手段包括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方法的主要区别在于,其接收的第二个参数必须是一个类数组对象。
  • 除了参数传递形式的差异外,两者的核心逻辑几乎一致。因此,在编写自定义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的指向值,从第二个参数开始,依次是执行函数所需的形参。
  • 与call和apply不同,bind方法在调用后不会立即执行函数,而是返回一个新的函数供后续调用。

语法:

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()外,该函数还可以作为构造函数,通过new关键字进行实例化。

例如以下的实例化场景:

let newBind = person.myBind(obj, 1, 2, 3);
let n = new newBind();
  • 通常情况下,变量n应当是newBind函数通过new关键字创建的实例化对象,此时构造函数的this应指向该实例化对象。
  • 然而在return self.apply(obj, args.concat(newArgs)); 这一行代码中,我们硬编码了this指向为obj,这显然不符合预期行为。
  • 因此,我们必须考虑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;
      };

至此,关于JavaScript中改变this指向的三种方式及其源码实现的总结介绍完毕。如需了解更多JavaScript改变this指向的相关内容,欢迎查阅本站过往文章或继续浏览后续文章,感谢大家的支持!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章