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

 更新时间:2023年12月14日 11:23:55   作者:秋天的一阵风  
this 指向的值是可以通过手动方式去改变的,比如call、bind、apply方法,本文主要为大家介绍了这三种方式的具体实现步骤,需要的可以参考下

前言

在上篇文章 可能是你看过最完整的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();
  • 我们知道变量n正常来说应该是newBind函数的实例化对象,构造函数的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指向内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 获取JAVASCRIPT时间戳函数的5种方法

    获取JAVASCRIPT时间戳函数的5种方法

    JavaScript获得时间戳的方法有五种,后四种都是通过实例化时间对象new Date() 来进一步获取当前的时间戳,JavaScript处理时间主要使用时间对象Date,本文对js时间戳函数获取方法给大家介绍的非常详细,需要的朋友参考下吧
    2024-01-01
  • 详解webpack模块加载器兼打包工具

    详解webpack模块加载器兼打包工具

    这篇文章主要介绍了webpack模块加载器兼打包工具,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • JavaScript基础之AJAX简单的小demo

    JavaScript基础之AJAX简单的小demo

    这篇文章主要介绍了JavaScript基础之AJAX简单的小demo ,代码简单易懂,非常不错,具有参考借鉴价值,需要的朋友参考下
    2017-01-01
  • three.js设置物体的缩放和旋转代码示例

    three.js设置物体的缩放和旋转代码示例

    最近在用three.js做三维模型的时候,需要通过鼠标滑轮向前来控制视角朝鼠标的位置放大,然后通过鼠标滑轮向后将视角复原,这篇文章主要给大家介绍了关于three.js如何设置物体的缩放和旋转的相关资料,需要的朋友可以参考下
    2023-11-11
  • WebSocket超细致完整用法举例讲解(含原理+前端+后端+实战案例+避坑指南)

    WebSocket超细致完整用法举例讲解(含原理+前端+后端+实战案例+避坑指南)

    websocket是一种网络通讯协议,我们之前可能也学过,其他的一些协议,比如偏底层的TCP,UDP,而应用层的协议,用的最多的还是HTTP协议,这篇文章主要介绍了WebSocket超细致完整用法的相关资料,需要的朋友可以参考下
    2026-05-05
  • 深入理解JavaScript中的for循环

    深入理解JavaScript中的for循环

    这篇文章主要给大家深入的介绍了JavaScript中的for循环,其中包括ES5中的三种for循环,分别是简单for循环、for-in以及forEach,另外还详细介绍了ES6新增的一种循环:for-of ,有需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-02-02
  • 前端打断点debugger使用流程详细教程

    前端打断点debugger使用流程详细教程

    这篇文章主要介绍了前端打断点debugger使用的相关资料,包括如何设置断点、如何使用调试按钮以及如何查看变量,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2024-12-12
  • JS中使用cavas截图网页并解决跨域及模糊问题

    JS中使用cavas截图网页并解决跨域及模糊问题

    这篇文章主要介绍了JS中使用cavas截取网页并解决跨域以及模糊问题 ,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-11-11
  • js实现简单分页导航栏效果

    js实现简单分页导航栏效果

    这篇文章主要为大家详细介绍了js实现简单分页导航栏效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • js监控IE火狐浏览器关闭、刷新、回退、前进事件

    js监控IE火狐浏览器关闭、刷新、回退、前进事件

    本节主要介绍了js监控IE火狐浏览器关闭、刷新、回退、前进事件的方法
    2014-07-07

最新评论