keguigong

函数中的this指针到底指向哪儿?

  by  keguigong

this 的指向问题

this 在对象中使用的时候指向十分清晰,但是在函数中使用的话,容易让人感到迷惑。

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

MDN Web Docs

根据定义可以提炼几点:

  • 函数中 this 的值取决于函数的调用方式。即永远指向函数的调用者。
  • 函数的 this 可以通过 bind() 绑定某个具体的对象。
  • 箭头函数本身没有 this,该函数 this 的值取决于闭合词法上下文的值。即永远指向声明该函数的作用域的 this 指向的对象。

有明确调用者,指向调用者

函数的 this 与声明函数所在的位置无关,而与调用者有关。通过 obj.sayHello 调用则指向 obj,如果直接调用则指向 window

Example 1
var name = 'window'
const obj = {
  name: 'obj',
  sayHello: function () {
    console.log(this.name)
  }
}
const sayHello = obj.sayHello
 
obj.sayHello() // 'obj'
sayHello() // 'window'

如果存在多层调用,则始终指向最近的调用者。

Example 2
const obj1 = {
  name: 'obj1',
  obj2: {
    name: 'obj2',
    sayHello: function () {
      console.log(this.name)
    }
  }
}
 
obj1.obj2.sayHello() // 'obj2'

箭头函数并不会绑定 this

对于 Example 1,我们把 sayHello 改造为箭头函数。

var name = 'window'
const obj = {
  name: 'obj',
  sayHello: () => {
    console.log(this.name)
  }
}
const sayHello = obj.sayHello
 
obj.sayHello() // 'window'
sayHello() // 'window'

箭头函数中 this 的值取决于声明该函数所在的作用域指向的对象。此处,sayHello 直接作为对象的一个属性,obj 并不是一个作用域,所以作用域为 window

我们将箭头函数放置在一个函数作用域内,则作用域的 this 就是箭头函数的 this。箭头函数 say 在函数作用域 sayHello 中,所以 say 的指向作用域 sayHello 指向的对象 obj

var name = 'window'
const obj = {
  name: 'obj',
  sayHello: function () {
    let say = () => {
      console.log(this.name)
    }
    return say
  }
}
 
var sayHello = obj.sayHello()
sayHello() // 'obj'

可以通过改变作用域 this 的指向改变箭头函数的指向。

var name = 'window'
 
function foo() {
  this.name = 'function'
 
  const sayHello = () => {
    console.log(this.name)
  }
 
  sayHello()
}
 
foo() // 'window'
new foo() // 'function'

直接调用 foo()new foo() 让函数作用域 foothis 发生了改变,所以箭头函数的 this 也发生了改变。

注意,一定要是声明该函数所在的作用域指向的对象,并不是调用的位置。如果 say 声明在全局,那么他的作用域就是 window,只有作用域的 this 才会影响箭头函数的 this

var name = 'window'
const say = () => {
  console.log(this.name)
}
const obj = {
  name: 'obj',
  sayHello: function () {
    let s = say
    return s
  }
}
 
var sayHello = obj.sayHello()
sayHello() // 'window'

setTimeout 以及箭头函数的应用

针对内置函数 setTimeout 等,如果使用普通函数,则调用的时候 this 指向的是 window。直接使用箭头函数则化解这一问题,因为箭头函数的 this 是作用域的 this

var name = 'window'
const obj = {
  name: 'obj',
  sayHello1: function () {
    setTimeout(function () {
      console.log(this.name)
    })
  },
  sayHello2: function () {
    setTimeout(() => {
      console.log(this.name)
    })
  }
}
 
obj.sayHello1() // 'window'
obj.sayHello2() // 'obj'

显式指定 this 的指向

通过 applycallbind可以改变函数 this 的指向。对于箭头函数,不可绑定 this

const obj1 = { name: 'obj1' }
const obj2 = { name: 'obj2' }
function foo() {
  console.log(this.name)
}
 
foo.apply(obj1) // 'obj1'
foo.apply(obj2) // 'obj2'

DOM 的事件回调函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素。

function handleClick(e) {
  console.log(this === e.currentTarget) // true
  console.log(this === e.target) // true when target is currentTarget
}
 
const element = document.getElementById('#target')
element.addEventListener('click', handleClick)

严格模式下的 this

在非严格模式(Sloopy Mode)下,如果函数没有明确指定 this 的指向,它将指向全局对象(浏览器中为 window,node 环境中为 globalThis)。而在严格模式(Strict Mode)下,如果函数没有明确指定 this 的指向,它将保持为 undefined

'use strict'
function foo() {
  console.log(this) // undefined
}
// Or inside function scope
function bar() {
  'use strict'
  console.log(this) // undefined
}

练习

Prolem 1
class Foo {
  func1 = function () {
    return this
  }
  func2 = () => {
    return this
  }
}
 
const foo = new Foo()
const { func1, func2 } = foo
console.log(func1(), func2()) // undefined Foo { func1: [Function: func1], func2: [Function: func2] }
Problem 2
var name = 'window'
function func() {
  console.log(this.name)
}
 
const obj = {
  name: 'obj',
  method: function (fn) {
    fn()
    arguments[0]()
  }
}
 
obj.method(func, 1) // 'window' undefined(this ---> arguments)

参考链接