js 中 this 指向

321 阅读4分钟

前言

thisjs 的关键字之一,this 的值随着使用场景的变化而变化

全局环境(浏览器)

浏览器环境的顶层对象是 windowNodeglobal 对象。ES5认为全局环境和顶层对象是等价的,但是ES6开始之后顶层对象和全局环境属于两种环境。

var a = 1
const b = 2
let c = 3
this.d = 4
class E {}
console.log(this === window) // true
console.log(this.a, this.b, this.c, this.d, this.E) // 1 undefined undefined 4 undefined
function fun1() {
  console.log(a, b, c, d, E) // 1 2 3 4 E {}
}
fun1()
classDiagram
window <|-- a
window <|-- d


env <|-- a
env <|-- b
env <|-- c
env <|-- d
env <|-- E

这里可以发现 let constclass 申明的变量都在全局环境而不在顶层对象中,这是因为 ES6 的规定。推测之后新增的申明变量大概率仅存在于全局环境。import 引入的变量也属于全局变量

在全局环境中 this 指向 顶层对象,即 window,仅在浏览器的情况下

console.log(this === window) // true

函数

在函数的内部,this 的值取决于函数被调用的位置

正常函数的调用

function fun1 () {
    console.log(this === window)
    
    function fun3 () {
        console.log(this === window)
    }
    fun3()
}

function fun2() {
    fun1()
}
fun1() // true true
fun2() // true true
classDiagram
window <|.. fun1
window <|-- fun2

fun1 <|-- fun3

fun2 <|.. fun1

主动调用 fun1 和 fun2 的环境是全局环境,且这是在非严格环境下。

对象内的函数

对象内的函数的 this 指向调用者。

const obj = {
  a: {
    a: function () {
      console.log(this === obj.a)
    }
  },
  b: function () {
    console.log(this === obj)
  }
}

obj.a.a() // true
obj.b() // true
classDiagram
obj_a <|-- obj_a_a_this
obj <|-- obj_b_this

obj.a.a() 的调用者是 obj.a

class P {
  constructor() {
    console.log(this)
  }

  a = function () {
    return this
  }
}

const p = new P() // p

console.log(p.a() === p) // true

函数内的函数的 this

function fun1 () {
  function fun2() {
    console.log(this)
  }
  fun2()
}

fun1() // window
fun1.bind({})() // window
classDiagram
window <|-- fun1
obj <|.. fun1_this
fun1 <|-- fun2_this

箭头函数 () => {}

ES6 添加的箭头函数。

箭头函数是没有自己的 this 对象,不同于正常函数 this 的动态,箭头函数是静态的,固定的指向函数上层的 this

对象内的箭头函数

const obj1 = {
    a: function () {
        console.log(this === obj1)
    },
    b: () => {
        console.log(this === window)
    }
}
obj1.a() // true
obj1.b() // true
classDiagram
obj1 <|-- a_this

window <|-- this
this <|-- obj1_b_this

在对象内的常规函数被调用,this 指向调用的对象,然后如果是 obj.a.a() 这种的情况指向会是 obj.a

对象内的箭头函数的 this 指向会是这个对象所在作用域的 this。可以看下面这个例子

function fun1 () {
  const obj = {
    a: () => {
      console.log(this)
    }
  }
  obj.a()
}
fun1() // window
fun1.bind({})() // {}
classDiagram
window <|-- fun1
window <|.. fun1_this: 正常调用

obj <|.. fun1_this: bind

fun1_this <|-- obj_a_this

这里将 fun1 函数的 this 指向 {}。当我们访问箭头函数的时候就会找到 obj 所在作用域的 this 指向,即 {}

函数内的箭头函数

function fun1() {
  const fun2 = () => {
    console.log(this)
  }
  fun2()
}
fun1() // window
fun1.bind({})() // {}
classDiagram
window <|-- fun1
obj <|-- fun1_this
fun1_this <|-- fun2_this

函数内的箭头函数 this 指向 fun1 函数的 this。正常情况 fun1this 指向 window,当然可以被 bind() 改变 this 指向

bind apply call 调用箭头函数

const fun1 = () => {
  console.log(this)
}

fun1()

fun1.bind({}) // window
fun1.apply({}) // window
fun1.call({}) // window

对箭头函数使用改变 this 指向的方法是没有用的

常规函数 & 箭头函数 & bind & 严格模式 结合使用

非严格模式下调用

function fun1() {
  console.log('fun1', this)
  function fun2() {
    console.log('fun2', this)


    function fun3() {
      console.log('fun3', this)
    }
    fun3()

    const fun4 = () => {
      console.log('fun4', this)
    }
    fun4()
  }
  fun2()

  const fun5 = () => {
    console.log('fun5', this)

    function fun6() {
      console.log('fun6', this)
    }
    fun6()
  }
  fun5()
}

fun1.bind({})()

打印结果

fun1 {}
fun2 window
fun3 window
fun4 window
fun5 {}
fun6 window
  • fun1: fun1 中的 thisbind 指定成 {}
  • fun2: fun2 中的 this 指向 fun1fun1 指向 window
  • fun3: fun3 中的 this 指向 fun2fun2 指向 fun1fun1 指向 window
  • fun4: fun4 中的 this 指向 fun2 中的 thisfun2this 指向 fun1fun1 指向 window
  • fun5: fun5 中的 this 指向 fun1 中的 thisfun1 中的 thisbind 指定成 {}
  • fun6: fun6 中的 this 指向 fun5fun5 指向 fun1fun1 指向 window

严格模式下这些的 window 都会变成 undefined。因为严格模式下 fun1 指向了 undefined

classDiagram
window <|.. fun1: 非严格模式
window <|-- this
undefined <|.. fun1: 严格模式

fun1 <|-- fun2
fun1 <|-- fun2_this
fun1 <|-- fun5

obj <|-- fun1_this
fun1_this <|-- fun5_this

fun2 <|-- fun3_this
fun2 <|-- fun4_this

fun5 <|-- fun6_this

闭包

let obj1 = {
  a: function () {
    const that = this
    console.log(this === obj1)
    return function () {
      console.log(this === window)
      console.log(that === obj1)
    }
  },
  b: function () {
    return () => {
      console.log(this === obj1)
    }
  },
  c: () => {
    return () => {
      console.log(this === window)
    }
  }
}

obj1.a()() // true true true
obj1.b()() // true
obj1.c()() // true

闭包即匿名函数不会绑定到对象上,所以还是指向 window 对象。

上面的 obj1.a() 把函数内的 this 绑定到变量上,闭包就能直接使用了,同样也可以用箭头函数。这是在 this 对象变化的时候常用的处理方式

常规函数 & 箭头函数 & 对象 & 严格模式 & 闭包

const obj = {
  a: function () {
    return function () {
      console.log(this)
    }
  },
  b: function () {
    return () => {
      console.log(this)
    }
  },
  c: () => {
    return function () {
      console.log(this)
    }
  },
  d: () => {
    return () => {
      console.log(this)
    }
  }
}

obj.a()() // window
obj.b()() // obj
obj.c()() // window
obj.d()() // window
classDiagram
window <|.. obj_a
undefined <|.. obj_a
obj_a <|-- obj_a_f_this

obj <|-- obj_b_this
obj_b_this <|-- obj_b_f_this

window <|.. obj_c
undefined <|.. obj_c
obj_c <|-- obj_c_f_this

window <|-- this
this <|-- obj_d_this
obj_d_this <|-- obj_d_f_this

严格模式

'use strict'
function fun1 () {
    console.log(this)
}
fun1() // undefined

const fun2 = () => {
    console.log(this === window)
}
fun2() // true

;(function () {
    console.log(this) // undefined
})()

之前的情况都不是严格模式,在严格模式之下函数内的 this 的值为 undefined

这里的常规函数和闭包都是 undefined,但是箭头函数却没有,因为箭头函数指向的是上层的 this,就是 window

HTML 事件

const btn = document.getElementById('btn')

btn.addEventListener('click', () => {
    console.log(this === window) // true
})
btn.addEventListener('click', function () {
    console.log(this === btn) // true
})

HTML 事件的回调函数中 this 指向的是触发事件的元素,我们可以用箭头函数修改指向

globalThis 对象

固定的顶层对象

总结

  1. this 的值是一种当调用当前环境的引用,即谁调用 this 所在的环境那么 this 就指向它。全局环境可以理解成一个函数

  2. 严格模式规则需要熟悉,以及哪些情况会触发严格模式。例如 babel 的打包

  3. this 单独使用的情况比较好理解,当多个情况结合使用的时候就考验我们基础扎不扎实了