Appearance
JavaScript 函数的 this 详解
一、函数的 this 是什么?
1. this 的定义
this 是 JavaScript 中的一个关键字,它是一个在函数运行时自动创建的内部对象。简单来说,this 就是函数执行时的上下文对象(context),代表当前函数执行时所属的对象。
2. this 的本质
javascript
function showThis() {
console.log(this)
}
showThis()在浏览器环境中:
- 非严格模式下,
this指向window对象 - 严格模式下,
this是undefined
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() | 全局对象/undefined | undefined 或报错 |
关键理解:
- 函数
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 指向 objfunc():调用时,没有点号,独立调用,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() | person | person | "张三" |
anotherPerson.sayName() | anotherPerson | anotherPerson | "李四" |
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 指向 objsetTimeout的回调函数在定时器内部独立调用,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()四种修正方法:
- 使用箭头函数
- 使用 bind 绑定 this
- 使用闭包保存 this(const self = this)
- 使用 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
}- 创建一个全新的空对象
- 将这个空对象的
[[Prototype]]链接到构造函数的 prototype - 将这个空对象绑定到函数的 this
- 如果函数没有返回对象,则自动返回这个新对象
规则五:箭头函数绑定(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 指向 arguments | 4 |
关键点:
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",因为:
第一次调用:
- b 实例上有
sayName方法(来自 A 构造函数中的this.sayName) - 输出实例的 name 属性 "B"
- b 实例上有
第二次调用:
- 仍然优先使用实例上的
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 | 特殊(定义时确定) |
核心要点
- this 在调用时确定,而不是定义时
- 箭头函数例外,它的 this 在定义时就确定了
- 优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
- 箭头函数无法被改变 this 指向
- 隐式丢失:函数赋值或作为参数传递时可能丢失 this
- 严格模式:默认绑定时 this 是 undefined
this指向判断规则:
普通函数调用 → this 指向 window / undefined(严格模式)
箭头函数 → this 继承外层(最特殊)
new 调用 → this 指向新对象
call / apply / bind → this 被显式指定
对象调用(obj.fn) → this 指向 obj