关于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实例化对象时会报错

相关推荐
周亚鑫9 分钟前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
落魄小二18 分钟前
el-table 表格索引不展示问题
javascript·vue.js·elementui
y52364819 分钟前
Javascript监控元素样式变化
开发语言·javascript·ecmascript
fruge33 分钟前
纯css制作声波扩散动画、js+css3波纹催眠动画特效、【css3动画】圆波扩散效果、雷达光波效果完整代码
javascript·css·css3
neter.asia42 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
嚣张农民1 小时前
JavaScript中Promise分别有哪些函数?
前端·javascript·面试
光影少年1 小时前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
Rattenking1 小时前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
熊的猫2 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
别拿曾经看以后~4 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui