原生js:this指向

109 阅读4分钟

this的指向基本规则:谁调用了函数,函数内的this就指向谁

一、全局作用域下this的指向

在全局作用域下,this===window,并且self和frames也是window,另外,ES11中globalThis也是指向window

image.png

环境如何获取全局对象
webwindow/self/frames/this
nodeglobal
workerself
通用globalThis(在node v12+中,global === globalThis)

二、全局函数中的this

1、非严格模式下,this === window

function test() {
  console.log(this === window) // true
}

test()

2、严格模式下,函数直接调用,this指向undefined,通过window调用,this指向window

'use strict'
function test() {
  return this
}

test() // undefined
window.test() // window

三、对象中的this指向最近的引用

1、对象方法中的this指向该对象

const obj = {
  name: '小明',
  getName: function () {
    console.log(this.name) // 小明
  }
}

obj.getName()

2、对象的方法中的闭包中的this默认指向window

function test() {
  function eat() {
    console.log(this) // window
  }
  eat()
}

const obj = {}
obj.test = test

obj.test()

但是当闭包作为对象的方法时,闭包中的this又指向该对象:

function test() {
  const person = {
    eat: function () {
      console.log(this) // person对象
    }
  }
  person.eat()
}

const obj = {}
obj.test = test

obj.test()

3、对象原型上的方法中的this指向该对象

const obj = Object.create({
  getSum: function () {
    console.log(this.a + this.b) // 3
  }
})
obj.__proto__.a = 1
obj.__proto__.b = 2

obj.getSum()

四、构造函数中的this指向实例对象

如果说构造函数中的this指向实例对象,这还不够严谨,通过new操作符在构造函数中首先创建一个空对象,然后将this指向这个空对象,最后将this返回出去赋值给了外界的变量,这个变量就是实例对象,实例对象就是返回的this

function Test() {
  this.name = '小明'
  this.getName = function () {
    console.log(this.name)
  }
}
const t = new Test()
t.getName()
  • 函数中不写return语句,默认return undefined;如果在函数执行前加上new操作符,那么这个函数中默认return this。
  • 如果一个函数是用来当做构造函数使用,就会结合new操作符,默认return this,这句可以不写,系统会自动加上;如果返回的是一个简单数据类型,会被忽略,还是return this;如果返回复杂数据类型,那么new操作符接收到的就是该复杂类型数据。
  • new Test()时,Test函数由window调用,但是new会在Test内做一些事情
    1. 创建一个空对象
    2. 将this指向这个空对象,逐行执行将构造函数中的属性和方法添加到这个空对象中,并且将构造函数的prototype赋值给对象的__proto__
    3. return this

五、ES6类中,this指向实例对象,但静态方法中的this指向当前类

六、事件处理函数中的this指向被绑定的DOM对象

    <button>按钮</button>
    <script>
      const btn = document.querySelector('button')
      btn.addEventListener('click', function () {
        console.log(this)
      })
    </script>

七、回调函数(除了事件的回调)中的this被重定向到window

  • 数组的所有方法:forEach、map、sort、reduce、filter、some、every
  • 定时器:setInterval、setTimeOut
  • Promise:异步前和异步后都是指向window
const arr = [1, 2, 3]

arr.forEach((item) => {
  console.log(this, item)
})

setTimeout(function () {
  console.log(this)
}, 100)

八、call、apply、bind可以改变this的指向

  1. 这三个方法的优先级高于基本规则
  2. 要注意bind返回的函数可以当做构造函数使用,通过new操作符又会改变this的指向,此时this指向构造函数中新创建的空对象

九、ES6箭头函数中的this指向外部作用域的this

1、箭头函数不管基本规则,也不管call/apply/bind,一直指向外部作用域的this,优先级最高

var a = 10
var b = 20

const obj = {
  a: 1,
  b: 2,
  getObjSum: function () {
    console.log(this.a + this.b)
  },
  getSum: () => {
    console.log(this.a + this.b) // this指向上级this,obj所在作用域的this指向window
  }
}

obj.getObjSum() // 3
obj.getSum() // 30

2、箭头函数不适用的场景

  1. 箭头函数不能当做构造函数使用。new操作符干了啥第二步:new操作符会将this指向由window改为实例对象,但是箭头函数中的this永远指向它的上一级this。所以箭头函数在设计时就不允许通过new执行,硬要执行会抛错:
Uncaught TypeError: Person is not a constructor
  1. 原型方法的定义也不能使用箭头函数。当原型的方法被实例调用时,我们希望this是指向该实例的,但是箭头函数会使this指向上一级,这不符合我们的期望
  2. 当想要使用arguments时,也不能用箭头函数。但是你可以使用剩余运算符(...)获取实参
  3. 事件的回调中,如果要对DOM进行操作,这种场景也不要使用箭头函数
  4. 对象的方法中,如果要使用对象中的某个属性,这种场景也不要使用箭头函数
  5. 如果需要使用call/apply/bind改变this指向,方法定义时也不要使用箭头函数,因为箭头函数的优先级最大
  6. 当使用forEach、map等方法时,如果传了第二个参数,就不能使用箭头函数
  7. 总结:箭头函数中没有this,它的this指向上一级this,如果说你的函数中需要使用当前this,那么就不要使用箭头函数