第四章:this 指向
1. 它是什么
this 本质上是:函数在执行时绑定的一个运行时对象引用 你可以先把它理解成:this 不是在函数声明时固定好的,而是大多数情况下在函数调用时决定的
比如:
function fn() {
console.log(this)
}单看这个函数定义,你不知道 this 是谁。
必须看它“怎么被调用”。
2. 为什么这样设计
因为 JS 里的函数很灵活:
- 可以单独调用
- 可以作为对象方法调用
- 可以被
call/apply/bind改变调用者 - 可以被 new 调用
- 可以作为回调传来传去
如果函数内部不能知道“这次是谁在调用我”,很多场景就不好写。
例如对象方法:
const user = {
name: 'Tom',
say() {
console.log(this.name)
}
}这里如果没有 this,say 很难自然地访问“当前对象”的数据。
所以 this 的设计目标就是:让同一个函数在不同调用场景下,可以拿到当前这次调用关联的上下文对象
3. 底层怎么运行
你先记住一句总原则:普通函数的 this,看调用方式;箭头函数的 this,看定义位置。 这是整章最核心的一句。
4. 普通函数 this 的 4 种绑定规则
面试里最常讲这 4 类:
- 默认绑定
- 隐式绑定
- 显式绑定
new绑定
4.1 默认绑定
普通函数直接调用时,走默认绑定。
function fn() {
console.log(this)
}
fn()浏览器非严格模式
通常是 window
严格模式 是 undefined
所以你面试里最好别死背“默认就是 window”,更严谨说法是:普通函数直接调用时,this 走默认绑定。在浏览器非严格模式下通常指向 window,在严格模式下是 undefined。
这个很重要。
4.2 隐式绑定
当函数作为某个对象的方法被调用时,this 指向调用它的那个对象。
const user = {
name: 'Tom',
say() {
console.log(this.name)
}
}
user.say() // Tom这里 this === user
注意关键词不是“函数定义在哪”,而是:点前面是谁,调用时 this 通常就是谁
4.3 隐式绑定丢失
这是面试很爱考的坑。
const user = {
name: 'Tom',
say() {
console.log(this.name)
}
}
const fn = user.say
fn()这里输出不是 Tom。
为什么?
因为:
user.say()这样调用时,才是隐式绑定const fn = user.say只是把函数拿出来fn()是普通函数直接调用,变成默认绑定了
所以你要记住:this 不是看函数原来属于谁,而是看这次怎么调用
4.4 显式绑定
可以用 call、apply、bind 人为指定 this。
function fn() {
console.log(this.name)
}
const obj = { name: 'Tom' }
fn.call(obj) // Tom
fn.apply(obj) // Tomcall
立即调用,参数一个个传
fn.call(obj, a, b)apply
立即调用,参数以数组传
fn.apply(obj, [a, b])bind
不立即调用,返回一个新函数,并永久绑定 this
const newFn = fn.bind(obj)
newFn()面试标准区分:call 和 apply 都是立即执行,区别在于传参方式不同;bind 不会立即执行,而是返回一个绑定了 this 的新函数。
4.5 new 绑定
当函数通过 new 调用时,this 指向新创建的实例对象。
function Person(name) {
this.name = name
}
const p = new Person('Tom')
console.log(p.name) // Tom这里 this === p
你可以先记住new做了几件事:
- 创建一个新对象
- 让新对象的原型指向构造函数的 prototype
- 把构造函数里的 this 绑定到这个新对象
- 执行函数体
- 默认返回这个新对象 后面讲原型链时我们会再展开。
5. 箭头函数为什么特殊
箭头函数没有自己的 this。
这是整章另一个核心结论。
const fn = () => {
console.log(this)
}箭头函数的 this 不是调用时决定的,而是:在定义时,继承外层普通作用域中的 this
也就是说:
- 普通函数:
this看调用方式 - 箭头函数:
this看定义位置的外层this例如:
const obj = {
name: 'Tom',
say: () => {
console.log(this.name)
}
}
obj.say()这里很多人以为输出 Tom,其实通常不是。
因为箭头函数 say 没有自己的 this,它不会因为 obj.say() 就把 this 绑定成 obj。它只会继承定义时外层的 this。
所以:箭头函数不适合拿来定义对象方法,除非你非常明确地就是要继承外层 this。
6. 为什么箭头函数这样设计
因为普通函数在回调场景里,this 很容易丢。
比如以前常见写法:
function Timer() {
this.count = 0
setInterval(function () {
this.count++
}, 1000)
}这里回调函数里的 this 通常不是外面的实例对象。
以前常用:
const self = this或者:
setInterval(function () {
console.log(this)
}.bind(this), 1000)箭头函数就是为了解决这种问题:
function Timer() {
this.count = 0
setInterval(() => {
this.count++
}, 1000)
}因为箭头函数直接继承外层 this。
所以箭头函数的设计目的之一就是:避免回调中 this 丢失
7. this 和作用域的区别
这个点你必须分清。 很多人把 this 和作用域混在一起,这是错的。 作用域 决定变量从哪里找 this 决定函数执行时的上下文对象是谁 例如:
const name = 'global'
const obj = {
name: 'Tom',
say() {
const name = 'inner'
console.log(name)
console.log(this.name)
}
}
obj.say()输出:
inner
Tom解释:
name是普通变量,按作用域规则找this.name是对象属性,按this指向找 所以你要记住:作用域链解决“变量从哪找”,this 解决“当前调用者是谁”
8. this 绑定优先级
如果多个规则同时出现,优先级要分清。
常见优先级可以这么记:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定 例如:
显式绑定比隐式绑定强
function fn() {
console.log(this.name)
}
const obj1 = { name: 'A', fn }
const obj2 = { name: 'B' }
obj1.fn.call(obj2) // B虽然本来 obj1.fn() 会指向 obj1,但 call(obj2) 强行改成了 obj2。
new 绑定比 bind 更高
这个属于进阶点,先知道即可:
function Person(name) {
this.name = name
}
const obj = {}
const Fn = Person.bind(obj)
const p = new Fn('Tom')
console.log(p.name) // Tom这里 new 会优先生成新实例。
9. 面试高频坑题
题 1
var name = 'global'
const obj = {
name: 'Tom',
say() {
console.log(this.name)
}
}
const fn = obj.say
fn()结果:
- 浏览器非严格模式下通常是
global或全局对象上的值 - 严格模式下可能报错或是
undefined核心原因:隐式绑定丢失,退回默认绑定
题 2
const obj = {
name: 'Tom',
say: () => {
console.log(this.name)
}
}
obj.say()结果通常不是 Tom
原因:箭头函数没有自己的 this,它继承定义时外层 this,而不是调用时的 obj
题 3
function fn() {
console.log(this.name)
}
const obj = { name: 'Tom' }
const newFn = fn.bind(obj)
newFn()结果:Tom 原因: bind 返回了一个永久绑定 obj 的新函数`
10. call、apply、bind 的本质区别
它是什么 都是改变函数执行时 this 的方法。
区别
call:立即执行,参数逐个传apply:立即执行,参数数组传bind:不立即执行,返回新函数 面试标准回答它们的共同点是都可以显式指定函数执行时的 this。不同点在于 call 和 apply 会立即执行,区别只是传参形式不同;bind 不会立即执行,而是返回一个新的绑定函数。
11. 实际开发里怎么用
- 对象方法里访问当前对象
const user = {
name: 'Tom',
say() {
console.log(this.name)
}
}- 回调里避免 this 丢失 优先用箭头函数:
class Demo {
constructor() {
this.count = 0
}
start() {
setInterval(() => {
this.count++
}, 1000)
}
}- 手动绑定事件处理函数
this.handleClick = this.handleClick.bind(this)这个在类组件、老代码里很常见。
12. 面试高频题:this 到底是什么
标准回答this 不是函数定义时固定的,而是函数执行时绑定的一个上下文对象引用。对于普通函数来说,this 的指向主要取决于调用方式;对于箭头函数来说,它没有自己的 this,而是继承定义时外层作用域的 this。
13. 面试高频题:箭头函数和普通函数的 this 有什么区别
标准回答普通函数的 this 在调用时确定,取决于调用方式;箭头函数没有自己的 this,它会在定义时从外层作用域继承 this,因此不能通过 call、apply、bind 或作为对象方法调用来改变它的 this 指向。
14. 这一章你必须背熟的 8 句话
- this 是函数执行时绑定的上下文对象引用。
- 普通函数的 this 主要看调用方式,不看定义位置。
- 对象方法调用时,this 通常指向调用它的对象。
- 函数一旦脱离对象单独调用,通常会发生 this 丢失。
- call、apply、bind 都可以显式指定 this。
- bind 不会立即执行,而是返回一个新函数。
- new 调用时,this 指向新创建的实例对象。
- 箭头函数没有自己的 this,它继承定义时外层的 this。
15. 你先自己回答这 6 题
你按自己的话答,我再像前几章一样逐题纠偏:
this 到底是什么?
this 是函数执行时绑定的上下文对象引用。普通函数的 this 主要取决于调用方式,箭头函数没有自己的 this,而是继承定义时外层环境的 this。普通函数的 this 是在定义时确定,还是调用时确定?
普通函数的 this 是在调用时确定的,它主要由调用方式决定,而不是由函数定义的位置决定。
- call、apply、bind 有什么区别?
call、apply、bind 都可以显式改变函数执行时的 this。call 和 apply 会立即执行,区别在于 call 逐个传参,apply 以数组形式传参;bind 不会立即执行,而是返回一个绑定了 this 的新函数。
- 为什么 const fn = obj.say; fn() 往往会丢失 this?
因为 this 不看函数原来属于哪个对象,而看当前的调用方式。obj.say 在赋值给 fn 后,再通过 fn() 调用时,已经不再是对象方法调用,而是普通函数调用,所以原本的隐式绑定丢失,this 会退回默认绑定。
- 箭头函数的 this 和普通函数有什么本质区别?
普通函数有自己的 this,this 在调用时根据调用方式确定;箭头函数没有自己的 this,它会在定义时继承外层执行上下文中的 this,之后不能再通过 call、apply、bind 或 new 去改变。
- 为什么说 this 和作用域链不是一回事?
作用域链解决的是变量查找问题,也就是当前作用域找不到变量时,沿着外层词法环境逐级查找;this 解决的是函数执行时上下文对象是谁的问题,它不参与普通变量的词法查找规则。
