Skip to content

JavaScript 函数的 this 详解

一、函数的 this 是什么?

1. this 的定义

this 是 JavaScript 中的一个关键字,它是一个在函数运行时自动创建的内部对象。简单来说,this 就是函数执行时的上下文对象(context),代表当前函数执行时所属的对象。

2. this 的本质

javascript
function showThis() {
  console.log(this)
}

showThis()

在浏览器环境中:

  • 非严格模式下,this 指向 window 对象
  • 严格模式下,thisundefined

3. this 的作用

this 让函数可以根据调用方式的不同,动态地引用不同的对象,实现代码复用:

javascript
function greet() {
  console.log('你好,我是 ' + this.name)
}

const person1 = { name: '张三', greet }
const person2 = { name: '李四', greet }

person1.greet()
person2.greet()

同一个 greet 函数,在不同的对象上调用,this 指向不同的对象,实现了代码复用。


二、this 是声明时确定还是调用时确定?

核心结论

this 的指向是在函数调用时确定的(除了箭头函数例外),而不是在函数声明时确定的。

这是理解 this 的最重要的一点!

示例说明

javascript
function sayThis() {
  console.log(this.name)
}

const obj1 = { name: '对象1', sayThis }
const obj2 = { name: '对象2', sayThis }

obj1.sayThis()
obj2.sayName()

sayThis()

解析:

调用方式this 指向输出结果
obj1.sayThis()obj1"对象1"
obj2.sayThis()obj2"对象2"
sayThis()全局对象/undefinedundefined 或报错

关键理解:

  • 函数 sayThis 在声明时,this 的指向是不确定
  • 只有当函数被调用时,this 才会根据调用方式被确定
  • 同一个函数,不同的调用方式,this 可能完全不同

再看一个例子

javascript
const obj = {
  name: '对象',
  sayName: function () {
    console.log(this.name)
  }
}

const func = obj.sayName

obj.sayName()
func()

解析:

  • obj.sayName():调用时,点号前面是 obj,所以 this 指向 obj
  • func():调用时,没有点号,独立调用,this 指向全局对象

这说明:this 的绑定发生在调用时,而不是函数定义时或赋值时。


三、"谁调用就指向谁" 对吗?举例说明

1. 基本理解

"谁调用就指向谁" 是一个简化的理解方式,在大多数情况下是正确的。

更准确的说法:this 指向调用该函数的对象(即函数调用时点号前面的对象)。

2. 正确的例子

javascript
const person = {
  name: '张三',
  sayName: function () {
    console.log(this.name)
  }
}

person.sayName()

const anotherPerson = { name: '李四' }
anotherPerson.sayName = person.sayName
anotherPerson.sayName()

const sayNameFunc = person.sayName
sayNameFunc()

解析:

调用方式调用者this 指向输出
person.sayName()personperson"张三"
anotherPerson.sayName()anotherPersonanotherPerson"李四"
sayNameFunc()无(独立调用)全局对象undefined

3. 需要注意的"陷阱"

虽然"谁调用就指向谁"在很多情况下适用,但以下情况需要特别注意:

陷阱一:回调函数中的 this

javascript
const person = {
  name: '张三',
  friends: ['李四', '王五'],
  showFriends: function () {
    console.log('showFriends 的 this:', this.name)

    this.friends.forEach(function (friend) {
      console.log(this.name + ' 的朋友: ' + friend)
    })
  }
}

person.showFriends()

输出:

showFriends 的 this: 张三
undefined 的朋友: 李四
undefined 的朋友: 王五

解析:

  • showFriends 的 this 确实指向 person(person 调用的)
  • 但 forEach 的回调函数中,this 不是 person
  • 因为回调函数是由 forEach 内部调用的,不是 person 调用的

陷阱二:定时器中的 this

javascript
const obj = {
  name: '对象',
  sayName: function () {
    console.log(this.name)
  },
  delayedSayName: function () {
    setTimeout(function () {
      console.log(this.name)
    }, 100)
  }
}

obj.sayName()
obj.delayedSayName()

输出:

对象
undefined

解析:

  • obj.sayName():obj 调用,this 指向 obj
  • setTimeout 的回调函数在定时器内部独立调用,this 指向全局对象

陷阱三:事件处理函数中的 this

javascript
const button = document.querySelector('button')

const obj = {
  name: '对象',
  handleClick: function () {
    console.log(this.name)
  }
}

button.addEventListener('click', obj.handleClick)

点击按钮后输出:undefined(或按钮元素的属性)

解析: 事件处理函数中的 this 指向绑定事件的 DOM 元素,而不是 obj

4. 修正 this 指向的方法

javascript
const person = {
  name: '张三',
  friends: ['李四', '王五'],

  showFriends: function () {
    this.friends.forEach(friend => {
      console.log(this.name + ' 的朋友: ' + friend)
    })
  },

  showFriends2: function () {
    this.friends.forEach(
      function (friend) {
        console.log(this.name + ' 的朋友: ' + friend)
      }.bind(this)
    )
  },

  showFriends3: function () {
    const self = this
    this.friends.forEach(function (friend) {
      console.log(self.name + ' 的朋友: ' + friend)
    })
  },

  showFriends4: function () {
    this.friends.forEach(function (friend) {
      console.log(this.name + ' 的朋友: ' + friend)
    }, this)
  }
}

person.showFriends()
person.showFriends2()
person.showFriends3()
person.showFriends4()

四种修正方法:

  1. 使用箭头函数
  2. 使用 bind 绑定 this
  3. 使用闭包保存 this(const self = this)
  4. 使用 forEach 的第二个参数

四、函数 this 的指向如何确定

this 绑定的五种规则

JavaScript 中 this 的绑定遵循以下五种规则:

规则一:默认绑定(Default Binding)

当函数独立调用时(没有任何修饰),应用默认绑定。

javascript
function showThis() {
  console.log(this)
}

showThis()
;(function () {
  'use strict'
  function strictShowThis() {
    console.log(this)
  }
  strictShowThis()
})()

规则:

  • 非严格模式:this 指向全局对象(window / global)
  • 严格模式:this 指向 undefined

规则二:隐式绑定(Implicit Binding)

当函数作为对象的方法调用时,this 绑定到该对象。

javascript
const obj = {
  name: '对象',
  sayName: function () {
    console.log(this.name)
  }
}

obj.sayName()

const obj2 = { name: '对象2' }
obj2.sayName = obj.sayName
obj2.sayName()

规则: this 绑定到函数调用时的上下文对象(点号前面的对象)

隐式丢失问题:

javascript
const obj = {
  name: '对象',
  sayName: function () {
    console.log(this.name)
  }
}

const func = obj.sayName
func()

setTimeout(obj.sayName, 100)

const arr = [obj.sayName]
arr[0]()

解析:

  • func():赋值后独立调用,this 丢失
  • setTimeout(obj.sayName, 100):回调函数独立调用
  • arr[0]():this 指向 arr 数组本身

规则三:显式绑定(Explicit Binding)

使用 call、apply、bind 方法显式指定 this。

javascript
function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation)
}

const person = { name: '张三' }

greet.call(person, '你好', '!')
greet.apply(person, ['你好', '!'])

const greetPerson = greet.bind(person)
greetPerson('你好', '!')

call、apply、bind 的区别:

方法参数形式是否立即执行返回值
call逐个传递函数的返回值
apply数组传递函数的返回值
bind逐个传递新函数

硬绑定(Hard Binding):

javascript
function sayThis() {
  console.log(this.name)
}

const obj1 = { name: '对象1' }
const obj2 = { name: '对象2' }

const boundFunc = sayThis.bind(obj1)
boundFunc.call(obj2)
boundFunc.apply(obj2)

const reboundFunc = boundFunc.bind(obj2)
reboundFunc()

解析: bind 返回的函数是硬绑定的,无法再被 call、apply 或 bind 改变 this

规则四:new 绑定(New Binding)

使用 new 关键字调用函数时,this 绑定到新创建的对象。

javascript
function Person(name) {
  this.name = name
  this.sayName = function () {
    console.log(this.name)
  }
}

const person1 = new Person('张三')
const person2 = new Person('李四')

person1.sayName()
person2.sayName()

new 操作符的执行过程:

javascript
function simulateNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)
  const result = Constructor.apply(obj, args)
  return result instanceof Object ? result : obj
}
  1. 创建一个全新的空对象
  2. 将这个空对象的 [[Prototype]] 链接到构造函数的 prototype
  3. 将这个空对象绑定到函数的 this
  4. 如果函数没有返回对象,则自动返回这个新对象

规则五:箭头函数绑定(Arrow Function Binding)

箭头函数没有自己的 this,它会捕获定义时外层作用域的 this

javascript
const obj = {
  name: '对象',
  sayName: function () {
    const arrowFunc = () => {
      console.log(this.name)
    }
    arrowFunc()
  },
  sayNameArrow: () => {
    console.log(this.name)
  }
}

obj.sayName()
obj.sayNameArrow()

解析:

  • sayName 中的箭头函数捕获的是 sayName 函数的 this(即 obj)
  • sayNameArrow 是箭头函数,捕获的是定义时的外层作用域(全局作用域)的 this

箭头函数的 this 特点:

javascript
const obj = {
  name: '对象',
  sayName: () => {
    console.log(this.name)
  }
}

obj.sayName()

const boundFunc = obj.sayName.bind({ name: '新对象' })
boundFunc()

const obj2 = { name: '对象2', sayName: obj.sayName }
obj2.sayName()

解析: 箭头函数的 this 在定义时就确定了,无法被 call、apply、bind 或作为对象方法调用而改变

this 绑定优先级

优先级从高到低:

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数不参与优先级比较,因为它在定义时就确定了 this,且无法被改变。

优先级验证:

javascript
function foo() {
  console.log(this.name)
}

const obj1 = { name: '对象1', foo }
const obj2 = { name: '对象2' }

obj1.foo.call(obj2)

const boundFoo = obj1.foo.bind(obj1)
const obj3 = { name: '对象3', foo: boundFoo }
obj3.foo()

const person = new boundFoo()
console.log(person.name)

解析:

  • obj1.foo.call(obj2):显式绑定优先级高于隐式绑定,输出 "对象2"
  • obj3.foo():硬绑定无法被隐式绑定改变,输出 "对象1"
  • new boundFoo():new 绑定优先级高于 bind,this 指向新对象

判断 this 的通用方法

当需要判断一个函数的 this 指向时,可以按照以下步骤:

1. 函数是否是箭头函数?
   - 是 → this 指向外层作用域的 this
   - 否 → 继续判断

2. 函数是否使用 new 调用?
   - 是 → this 指向新创建的对象
   - 否 → 继续判断

3. 函数是否使用 call、apply、bind?
   - 是 → this 指向指定的对象
   - 否 → 继续判断

4. 函数是否作为对象的方法调用(有上下文对象)?
   - 是 → this 指向该上下文对象
   - 否 → 应用默认绑定

5. 默认绑定:
   - 严格模式 → this 是 undefined
   - 非严格模式 → this 是全局对象

五、10 道经典 this 指向面试题及详解

题目 1:对象方法与箭头函数

javascript
var name = 'window'

var obj = {
  name: 'obj',
  sayName: function () {
    console.log(this.name)
  },
  sayNameArrow: () => {
    console.log(this.name)
  }
}

obj.sayName()
obj.sayNameArrow()

var sayName = obj.sayName
sayName()

答案:

obj
window
window

详解:

代码调用方式this 指向输出
obj.sayName()隐式绑定obj"obj"
obj.sayNameArrow()箭头函数外层作用域(全局)"window"
sayName()默认绑定全局对象"window"

关键点:

  • 对象字面量中的箭头函数,this 捕获的是外层作用域(全局作用域)的 this
  • 函数赋值后独立调用,应用默认绑定

题目 2:构造函数与原型

javascript
var name = 'window'

function Person(name) {
  this.name = name
  this.sayName = function () {
    console.log(this.name)
  }
  this.sayNameArrow = () => {
    console.log(this.name)
  }
}

Person.prototype.sayNameProto = function () {
  console.log(this.name)
}

Person.prototype.sayNameArrowProto = () => {
  console.log(this.name)
}

const person = new Person('person')

person.sayName()
person.sayNameArrow()
person.sayNameProto()
person.sayNameArrowProto()

const { sayName, sayNameArrow, sayNameProto, sayNameArrowProto } = person
sayName()
sayNameArrow()
sayNameProto()
sayNameArrowProto()

答案:

person
person
person
window
window
person
window
window

详解:

代码this 绑定方式this 指向输出
person.sayName()隐式绑定person 实例"person"
person.sayNameArrow()箭头函数捕获构造函数的 this(person)"person"
person.sayNameProto()隐式绑定person 实例"person"
person.sayNameArrowProto()箭头函数捕获全局作用域"window"
sayName()默认绑定全局对象"window"
sayNameArrow()箭头函数仍是 person 实例"person"
sayNameProto()默认绑定全局对象"window"
sayNameArrowProto()箭头函数全局作用域"window"

关键点:

  • 构造函数中的箭头函数捕获的是构造函数的 this(即实例)
  • 原型上的箭头函数捕获的是全局作用域的 this
  • 箭头函数的 this 在定义时就确定了,解构后不会改变

题目 3:返回函数的 this

javascript
var name = 'window'

const obj = {
  name: 'obj',
  sayName: function () {
    return function () {
      console.log(this.name)
    }
  },
  sayNameArrow: function () {
    return () => {
      console.log(this.name)
    }
  }
}

const fn1 = obj.sayName()
fn1()

const fn2 = obj.sayNameArrow()
fn2()

const fn3 = obj.sayName.call({ name: 'newObj' })
fn3()

const fn4 = obj.sayNameArrow.call({ name: 'newObj' })
fn4()

答案:

window
obj
window
newObj

详解:

代码分析输出
fn1()返回普通函数,独立调用,默认绑定"window"
fn2()返回箭头函数,捕获 sayNameArrow 的 this(obj)"obj"
fn3()call 改变了 sayName 的 this,但返回的函数仍独立调用"window"
fn4()call 改变了 sayNameArrow 的 this 为 newObj,箭头函数捕获这个 this"newObj"

关键点:

  • 普通函数返回的函数,this 在调用时确定
  • 箭头函数返回的函数,this 在定义时确定(捕获外层函数的 this)

题目 4:间接引用

javascript
var name = 'window'

function foo() {
  console.log(this.name)
}

const obj = {
  name: 'obj',
  foo
}

const obj2 = {
  name: 'obj2'
}

;(foo, obj.foo)()
;(obj2.foo = obj.foo)()
;(obj2.foo, obj.foo)()

答案:

window
window
window

详解:

这些都是间接引用的情况,应用默认绑定:

代码分析
(foo, obj.foo)()逗号表达式返回最后一个值 obj.foo,独立调用
(obj2.foo = obj.foo)()赋值表达式返回赋的值 obj.foo,独立调用
(obj2.foo, obj.foo)()逗号表达式返回 obj.foo,独立调用

关键点:

  • 逗号表达式和赋值表达式返回的是函数引用,不是调用
  • 这些函数引用被独立调用,应用默认绑定

题目 5:bind 硬绑定

javascript
var name = 'window'

const obj = {
  name: 'obj',
  sayName: function () {
    console.log(this.name)
  }
}

const boundSayName = obj.sayName.bind(obj)
const obj2 = { name: 'obj2', sayName: boundSayName }

obj2.sayName()
obj2.sayName.call({ name: 'obj3' })

const newSayName = new boundSayName()
console.log(newSayName.name)

答案:

obj
obj
undefined

详解:

代码分析输出
obj2.sayName()boundSayName 是硬绑定函数,this 无法被隐式绑定改变"obj"
obj2.sayName.call({ name: 'obj3' })硬绑定无法被 call 改变"obj"
new boundSayName()new 绑定优先级高于 bind,this 指向新对象undefined

关键点:

  • bind 返回的函数是硬绑定的,无法被 call、apply 或作为对象方法改变
  • 但 new 绑定的优先级高于 bind

题目 6:arguments 对象

javascript
var length = 10

function fn() {
  console.log(this.length)
}

const obj = {
  length: 5,
  method: function (fn) {
    fn()
    arguments[0]()
  }
}

obj.method(fn, 1, 2, 3)

答案:

10
4

详解:

代码分析输出
fn()fn 作为参数传递后独立调用,默认绑定,this 指向全局对象10
arguments[0]()arguments[0] 是 fn,作为 arguments 对象的方法调用,this 指向 arguments4

关键点:

  • arguments 是类数组对象,包含所有传入的参数
  • arguments.length 是参数个数:fn, 1, 2, 3 共 4 个
  • arguments[0]() 调用时,this 指向 arguments 对象

题目 7:原型链与继承

javascript
var name = 'window'

function A(name) {
  this.name = name || 'A'
  this.sayName = function () {
    console.log(this.name)
  }
}

A.prototype.sayName = function () {
  console.log('prototype: ' + this.name)
}

function B(name) {
  A.call(this, name)
}

B.prototype = Object.create(A.prototype)
B.prototype.constructor = B

const b = new B('B')
b.sayName()

B.prototype.sayName = function () {
  console.log('B prototype: ' + this.name)
}

b.sayName()

答案:

B
B

详解:

两次调用 b.sayName() 都输出 "B",因为:

  1. 第一次调用:

    • b 实例上有 sayName 方法(来自 A 构造函数中的 this.sayName
    • 输出实例的 name 属性 "B"
  2. 第二次调用:

    • 仍然优先使用实例上的 sayName 方法
    • 输出 "B"

关键点:

  • 实例属性优先于原型属性
  • 构造函数中 this.sayName 会添加到实例上

题目 8:嵌套对象中的 this

javascript
var name = 'window'

const obj = {
  name: 'obj',
  sayName: () => {
    console.log(this.name)
  },
  wrapper: {
    name: 'wrapper',
    sayName: function () {
      const arrow = () => {
        console.log(this.name)
      }
      arrow()
    },
    sayNameArrow: () => {
      console.log(this.name)
    }
  }
}

obj.sayName()
obj.wrapper.sayName()
obj.wrapper.sayNameArrow()

答案:

window
wrapper
window

详解:

代码分析输出
obj.sayName()箭头函数在对象字面量中定义,捕获全局作用域的 this"window"
obj.wrapper.sayName()普通函数中的箭头函数捕获该函数的 this(wrapper)"wrapper"
obj.wrapper.sayNameArrow()箭头函数捕获全局作用域的 this"window"

关键点:

  • 对象字面量不创建新的作用域
  • 箭头函数在对象字面量中定义时,捕获的是外层作用域(通常是全局作用域)的 this

题目 9:构造函数返回值

javascript
var name = 'window'

function Foo() {
  this.name = 'Foo'
  this.sayName = function () {
    console.log(this.name)
  }
  return this
}

const obj = {
  name: 'obj',
  sayName: function () {
    console.log(this.name)
  }
}

const foo = new Foo()
foo.sayName()

const foo2 = Foo()
foo2.sayName()

const foo3 = new Foo()
foo3.sayName.call(obj)

const foo4 = Foo.call(obj)
foo4.sayName()

答案:

Foo
window
obj
obj

详解:

代码分析输出
new Foo()new 绑定,返回新对象(虽然 return this,但 new 会忽略)"Foo"
Foo()默认绑定,返回全局对象"window"
foo3.sayName.call(obj)显式绑定,this 指向 obj"obj"
Foo.call(obj)显式绑定,Foo 内部的 this 指向 obj,返回 obj"obj"

关键点:

  • 构造函数如果返回对象,new 操作会返回该对象
  • 如果返回原始值或 this,new 操作返回新创建的对象

题目 10:综合题

javascript
var name = 'window'

function Person(name) {
  this.name = name
}

Person.prototype = {
  name: 'prototype',
  sayName: function () {
    console.log(this.name)
  },
  sayNameArrow: () => {
    console.log(this.name)
  }
}

const person = new Person('person')

const obj = {
  name: 'obj',
  sayName: person.sayName,
  sayNameArrow: person.sayNameArrow
}

person.sayName()
person.sayNameArrow()
obj.sayName()
obj.sayNameArrow()

const sayName = person.sayName
const sayNameArrow = person.sayNameArrow
sayName()
sayNameArrow()

person.sayName.call(obj)
person.sayNameArrow.call(obj)

答案:

person
window
obj
window
window
window
obj
window

详解:

代码绑定方式this 指向输出
person.sayName()隐式绑定person 实例"person"
person.sayNameArrow()箭头函数全局作用域"window"
obj.sayName()隐式绑定obj"obj"
obj.sayNameArrow()箭头函数全局作用域"window"
sayName()默认绑定全局对象"window"
sayNameArrow()箭头函数全局作用域"window"
person.sayName.call(obj)显式绑定obj"obj"
person.sayNameArrow.call(obj)箭头函数全局作用域"window"

关键点:

  • 原型上的箭头函数 this 捕获的是全局作用域
  • 箭头函数的 this 无法被 call、apply、bind 改变
  • 普通函数的 this 可以被显式绑定改变

六 、总结

this 绑定规则速记表

绑定类型触发条件this 指向优先级
默认绑定独立调用全局对象 / undefined(严格模式)最低
隐式绑定对象方法调用调用对象中低
显式绑定call/apply/bind指定的对象中高
new 绑定new 调用新创建的对象
箭头函数箭头函数外层作用域的 this特殊(定义时确定)

核心要点

  1. this 在调用时确定,而不是定义时
  2. 箭头函数例外,它的 this 在定义时就确定了
  3. 优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
  4. 箭头函数无法被改变 this 指向
  5. 隐式丢失:函数赋值或作为参数传递时可能丢失 this
  6. 严格模式:默认绑定时 this 是 undefined

this指向判断规则:

普通函数调用 → this 指向 window / undefined(严格模式)
箭头函数 → this 继承外层(最特殊)
new 调用 → this 指向新对象
call / apply / bind → this 被显式指定
对象调用(obj.fn) → this 指向 obj

基于 VitePress 的本地知识库