关于JS中this对象指向问题总结

一、前言

关于JS中this对象指向问题,相信做过项目的小伙伴多多少少都会遇到过,明明感觉代码写的没问题,可是运行的时候,就会报错,比如报错 xxx is not a function。

我最近也遇到了,百度学习了不少前辈对于this对象指向问题的解析,于是总结了这篇文章。

二、多种情况下使用this,指向有所不同

先简略概括下,this在英文中的意思是,"这,这个"的意思,在编程中我们通常把this成为当前对象。在这篇文章中,我们从始至终都要记得一句话:this永远指向,调用它的对象,默认指向window/全局对象。

如果有多层嵌套对象调用的话,this指向最后一次调用这个方法的对象。

1)全局作用域中的this

在全局作用域中,this指向 window/全局对象

javascript 复制代码
 console.log(this) // window对象
 console.log(this === window) //true

2)函数调用中的this

当一个函数不是一个对象的属性时,直接作为函数来调用;

函数是 普通函数 时,this指向window/全局对象;

函数是 箭头函数 时,绑定的是父作用域的this指向。

javascript 复制代码
function func(){
  console.log(this) ; //this指向window对象
}
func();

注意定时器内部的this永远指向window,比如setTimeout,setInterval

javascript 复制代码
setTimeout(function(){
   console.log(this); //window对象
 },1000)

3)对象中的this

如果一个函数作为一个对象的方法来调用时,this 指向这个对象;(比如vue中的methods对象里面定义的函数方法)

箭头函数除外,因为它会捕获其所在上下文的this,所以可能会指向window/全局对象。

javascript 复制代码
 let obj = {
      func: function () {
        console.log(this)
      }
    }
 obj.func()

在这段代码中,我们看到了 obj.func(),this 处在 func 函数的内部,那到底是谁调用的 func() 哪?显而易见是 obj,因为 this 永远指向,调用它的对象,所以最后的打印结果应该是 obj。

4)使用 new 实例化对象,构造函数中的this

构造函数中的this指向实例出来的对象。

javascript 复制代码
function Person () {
  console.log(this)
  this.name = '铁锤妹妹'
}
var obj = new Person() // 得到一个实例化对象,继承了Person函数的属性
console.log(obj)

打印结果:就是Person

5)apply 、call 和 bind 调用中的this

apply和call 改变的是函数运行时的this指向,bind返回一个this绑定了传入对象的新函数。

这个函数的this指向使用new时会被改变。

注意 :箭头函数中的this不能通过apply 、call 和 bind 改变,因为箭头函数中的this指向在定义时已经确认了,之后不会被改变

javascript 复制代码
const obj = { name: '铁锤妹妹', age: 18 }
function Person () {
  console.log(this.name)
}
Person.apply(obj) //铁锤妹妹
Person.call(obj) //铁锤妹妹
Person.bind(obj)() //铁锤妹妹

6)事件中的this

在事件处理函数中,this指向触发事件的目标对象<div></div>

javascript 复制代码
document.querySelector('div').onclick = function () {
  console.log(this) //<div></div>
}

总结:

1. 全局作用域中的this指向window
2. 普通函数this指向window,箭头函数指向它的上下文this
3. 对象中方法的this指向该方法所属的对象
4. 构造函数中的this指向实例出来的对象
5. 事件当中的this指向当前绑定的元素
6. 定时器中的this指向window
7. apply 、call 和 bind 调用中的this指向它想要指向的this

三、改变this指向的方法

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • new 实例化一个对象
  • 使用 apply、call、bind

1)箭头函数不绑定this,会捕获其所在上下文的this,作为自己的this

  • 这句话需要注意的是,箭头函数的外层如果有普通函数,那么箭头函数的this就是这个外层普通函数的this(它会继承自己作用域上一层的this);箭头函数的外层如果没有普通函数,那么箭头函数的this就是window/全局对象。
  • 箭头函数中的this指向在定义时已经确认了,之后不会被改变

下面这个例子是箭头函数外层有普通函数。

javascript 复制代码
 var name = 'windowsName'
 var a = {
      name: '铁锤妹妹',
      func1: function () {
        console.log(this.name)
      },
      func2: function () {
        setTimeout(() => {
          this.func1()
        }, 100)
      }
    }
    a.func2()  //铁锤妹妹

如果不使用箭头函数,运行会报错,原因是使用普通函数时,调用的setTimeout的对象是Window,而Window中没有定义func1函数。

javascript 复制代码
 var name = 'windowsName'
 var a = {
      name: '铁锤妹妹',
      func1: function () {
        console.log(this.name)
      },
      func2: function () {
        setTimeout(function () {
          this.func1()
        }, 100)
      }
    }
    a.func2()

报错信息

2)如果不想使用箭头函数,也可以在函数内部使用var _this = this

javascript 复制代码
 var name = 'windowsName'
 var a = {
      name: '铁锤妹妹',
      func1: function () {
        console.log(this.name)
      },
      func2: function () {
        var _this = this
        setTimeout(function () {
          _this.func1()
        }, 100)
      }
    }
    a.func2()  //铁锤妹妹

在 func2 中,首先设置 var _this = this,这里的this是调用 func2 的对象a,因为a是最后一次调用这个方法的对象;

为了防止在 func2 中的 setTimeout 被 window 调用而导致 setTimeout 中的 this 指向变为 window对象。我们将this(指向变量a)赋值给一个变量 _this,这样在 func2 中我们使用 _this 就是指向对象 a 了。

3)如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象

new操作符的执行过程:

  1. 首先创建了一个空的对象(创建一个新的存储空间)
  2. 设置原型,将对象的原型设置为函数的 prototype 对象(也就是将对象的__proto__属性
    指向构造函数的 prototype 属性)
  3. 让函数的this指向这个新空对象,执行构造函数的代码(为这个新对象添加属性和方法)
  4. 返回新对象(所以构造函数不需要return)

因此,使用new 实例化对象,构造函数中的this指向实例化对象。

注意:上面说的箭头函数除外,因为:
1)箭头函数没有自己的 this 指向,无法调用call、apply、bind更改this指向。
2)箭头函数没有 prototype 属性,而 new 命令在执行时需要将构造函数的 prototype 赋值给新对象的 _proto_

所以不能作为构造函数,不能使用new命令,否则抛出错误。

javascript 复制代码
//箭头函数使用new实例化报错代码
let func1 = () => {}
let func2 = new func1()
console.log(func2) // func1 is not a constructor

4)使用apply、call和bind指定 调用函数 的this指向

javascript 复制代码
    var year = 2023
    function getDate (month, day) {
      return this.year + '-' + month + '-' + day
    }

    let obj = { year: 1998 }
    getDate.call(null, 3, 8) // 2023-3-8
    getDate.call(obj, 3, 8) // 1998-3-8
    getDate.apply(obj, [6, 8]) // 1998-6-8
    
    getDate.bind(obj,3,8)() // 1998-3-8
    // 或者
    let boundGetDate = getDate.bind(obj)
    boundGetDate(3,8)  // 1998-3-8

// 可以看出
// call和apply区别是 参数的方式, apply使用数组传递参数,call是按顺序传递参数。
// bind 传参也是按顺序传递参数,但是bind()方法后还有个(),说明bind方法返回的是一个函数,并不执行,需要手动去调用才执行

使用 apply() 方法

apply接受两个参数,第一个是this的指向,第二个是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中);使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。

  • 语法规则 :
    fun函数名.apply(obj,[arg1,arg2...,argN])
  • 参数说明:
    obj :this要指向的对象
    [arg1,arg2...argN] : 参数列表,要求格式为数组,也可以是类数组。

使用 call() 方法

call 方法的第一个参数也是this的指向,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,表示指向window。和apply一样,call也只是临时改变一次this指向,并立即执行。

  • 语法规则 :
    fun函数名.call(obj,arg1,arg2...argN)
  • 参数说明:
    obj :this要指向的对象
    arg1,arg2...argN : 参数列表,参数与参数之间使用一个逗号隔开

区别 :call 和 apply的作用类似,都是 立即调用函数 并改变函数的this指向。唯一不同的是它们传递 函数实参参数 的方式不同。apply 的第二个参数是一个数组,call 的第二个及以后的参数是单独列举的。

使用 bind() 方法

第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数,调用新函数的时候才会执行目标函数。bind方法可以方便地在代码中传递和复用这个新函数。

使用 apply、call 和 bind 要注意以下几点:

1)只能在 函数 上使用,对于其他类型的值调用这些方法会抛出 TypeError 异常;

2)改变函数执行上下文可能会造成一些意外的后果,例如访问未定义变量、调用非法函数等;

3)使用 apply、call 和 bind 应该避免滥用,只在确有必要的情况下使用。过多使用这些方法可能会导致代码难以理解和维护,甚至降低程序性能。

通常来说,apply 和 call 常用于快速处理函数参数为数组或固定长度时,而 bind 则更适合用于需要重复使用同一个上下文的场景,例如事件处理器等。

call, apply, bind 三者的区别:

相同点:

  • 都可以改变函数内部 this 的指向。
  • 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文。
  • 都可以利用 后续参数 传参。

区别:

  • 后续参数的传递:apply使用数组传递参数;call和bind按顺序传递参数。
  • apply、call都是对函数的直接调用;bind方法返回的仍是一个函数(this指向已经变化的函数),需要手动调用。

三、关于setTimeout() 定时器的"this"指向问题

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字会指向 window (或全局) 对象,这和所期望的this的值是不一样的。

javascript 复制代码
var name = 'windowsName'
    var a = {
      name: '铁锤妹妹',
      func1: function () {
        setTimeout(function () {
          console.log(this.name, 'name')
        }, 100)
      }
    }
    a.func1()
    //因为setTimeout,所以this指向window
    //打印结果:windowsName

四、总结本文箭头函数需要注意的地方

1. 函数是箭头函数的话,指向它的上下文对象的this
2. 箭头函数中的this不能通过apply 、call 和 bind 改变,因为箭头函数中的this指向在定义时已经确认了,之后不会被改变
3. 箭头函数使用new实例化对象时会报错

相关推荐
王天平·Jason Wong20 分钟前
汉王、绘王签字版调用封装
开发语言·前端·javascript
**之火39 分钟前
ArrayBuffer 对象常见的几个用途
前端·javascript
xiongxinyu101 小时前
让一个元素水平垂直居中的方式
前端·javascript·css·面试
性野喜悲2 小时前
vue通过后台返回的数字显示不同的文字内容,多个内容用、隔开
前端·javascript·vue.js
袁亦袁2 小时前
通过属性透传,iview Table组件实现自适应高度
javascript·vue.js·iview
武汉前端开发蓝风2 小时前
前端Debugger时复制的JS对象字符转JSON对象
前端·javascript·json·debugger
爱编程的鱼2 小时前
HTML如何在图片上添加文字
前端·javascript·html
顶顶年华正版软件官方3 小时前
关键帧功能怎么使用 关键帧控制视频特效怎么用 会声会影视频剪辑软件教程
前端·javascript·音视频·学习方法·关键帧·会声会影教程·视频剪辑软件
来之梦3 小时前
uniapp自定义富文本现实组件(支持查看和收起)
前端·javascript·uni-app
森叶3 小时前
Electron开发 - 如何在主进程Main中让node-fetch使用系统代理
前端·javascript·electron