this指向
发表于更新于
常见们this指向
OHNIIJavaScript中的this指向
核心流程(记忆关键)
记忆口诀:普通函数看调用者,箭头函数看定义处,new 指向新对象
快速判断(一句话总结):
1 2 3
| 普通函数:谁调用指向谁,没人调用指向全局(严格模式 undefined) 箭头函数:定义时绑定,继承外层 this 构造函数:new 调用指向新创建的实例对象
|
1. this 指向快速判断(核心规律)
1.1 最常见的 4 种情况
情况1:普通函数调用 → 指向全局对象
1 2 3 4 5 6 7 8 9 10 11
| function foo() { console.log(this) } foo()
'use strict' function bar() { console.log(this) } bar()
|
情况2:对象方法调用 → 指向调用的对象
1 2 3 4 5 6 7
| const obj = { name: 'Tom', sayName() { console.log(this.name) } } obj.sayName()
|
情况3:构造函数调用 → 指向新创建的实例
1 2 3 4 5
| function Person(name) { this.name = name } const p = new Person('Tom') console.log(p.name)
|
情况4:箭头函数 → 继承外层 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const obj = { name: 'Tom', sayName: () => { console.log(this.name) }, sayAge() { const inner = () => { console.log(this.name) } inner() } } obj.sayName() obj.sayAge()
|
1.2 快速判断口诀
1 2 3 4 5 6
| 看函数怎么调用: 1. obj.fn() → this 是 obj 2. fn() → this 是 window/undefined 3. new Fn() → this 是新对象 4. fn.call(obj) → this 是 obj 5. 箭头函数 → this 看外层
|
2. this 绑定规则详解
2.1 默认绑定(独立函数调用)
规则:函数独立调用时,this 指向全局对象(严格模式下是 undefined)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function foo() { console.log(this) } foo()
const obj = { name: 'Tom', sayName() { console.log(this.name) } } const fn = obj.sayName fn()
|
2.2 隐式绑定(对象方法调用)
规则:作为对象方法调用时,this 指向该对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const obj = { name: 'Tom', sayName() { console.log(this.name) } } obj.sayName()
const obj1 = { name: 'Tom', child: { name: 'Jerry', sayName() { console.log(this.name) } } } obj1.child.sayName()
|
隐式丢失(常见坑):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const obj = { name: 'Tom', sayName() { console.log(this.name) } }
const fn = obj.sayName fn()
setTimeout(obj.sayName, 1000)
function execute(callback) { callback() } execute(obj.sayName)
|
2.3 显式绑定(call/apply/bind)
规则:通过 call、apply、bind 显式指定 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function sayName() { console.log(this.name) }
const obj1 = { name: 'Tom' } const obj2 = { name: 'Jerry' }
sayName.call(obj1)
sayName.apply(obj2)
const boundFn = sayName.bind(obj1) boundFn()
|
三者区别:
1 2 3 4 5 6 7 8 9
| function greet(age, city) { console.log(`${this.name}, ${age}岁, 来自${city}`) }
const person = { name: 'Tom' }
greet.call(person, 18, '北京') greet.apply(person, [18, '北京']) greet.bind(person, 18)('北京')
|
2.4 new 绑定(构造函数调用)
规则:使用 new 调用时,this 指向新创建的对象
1 2 3 4 5 6 7
| function Person(name) { this.name = name console.log(this) }
const p = new Person('Tom') console.log(p.name)
|
new 的执行过程:
1 2 3 4
| 1. 创建一个新对象 2. 将新对象的 __proto__ 指向构造函数的 prototype 3. 将构造函数的 this 指向新对象,并执行 4. 如果构造函数返回对象,则返回该对象,否则返回新对象
|
2.5 箭头函数(特殊规则)
规则:箭头函数没有自己的 this,继承外层作用域的 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const obj = { name: 'Tom', sayName: () => { console.log(this.name) }, sayAge() { const inner = () => { console.log(this.name) } inner() } }
obj.sayName() obj.sayAge()
|
箭头函数特点:
- 没有自己的 this,继承外层作用域
- this 在定义时确定,不是调用时确定
- 无法通过 call/apply/bind 改变 this
- 不能作为构造函数使用(不能 new)
3. this 绑定优先级
优先级从高到低:
1 2 3 4 5
| 1. new 绑定 → this 指向新对象 2. 显式绑定 → this 指向指定对象 3. 隐式绑定 → this 指向调用对象 4. 默认绑定 → this 指向全局/undefined 5. 箭头函数 → 继承外层 this(不参与优先级)
|
优先级验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function foo() { console.log(this.name) }
const obj1 = { name: 'Tom', foo } const obj2 = { name: 'Jerry' }
obj1.foo()
obj1.foo.call(obj2)
const boundFoo = foo.bind(obj1) const instance = new boundFoo()
|
4. 经典面试题(高频5题)
题目1:隐式丢失
1 2 3 4 5 6 7 8
| const obj = { name: 'Tom', sayName() { console.log(this.name) } }
obj.sayName() const fn = obj.sayName fn()
|
题目2:箭头函数陷阱
1 2 3 4 5 6 7 8 9 10 11
| const obj = { name: 'Tom', sayName: () => { console.log(this.name) }, sayAge() { return () => { console.log(this.name) } } }
|
题目3:setTimeout中的this
1 2 3 4 5 6 7 8 9 10 11 12 13
| const obj = { name: 'Tom', delayedSay() { setTimeout(function() { console.log(this.name) }, 1000) }, delayedSayArrow() { setTimeout(() => { console.log(this.name) }, 1000) } }
|
题目4:bind只生效一次
1 2 3 4 5 6 7 8
| function foo() { console.log(this.name) }
const obj1 = { name: 'Tom' } const obj2 = { name: 'Jerry' }
const boundFoo = foo.bind(obj1) boundFoo() boundFoo.call(obj2)
|
题目5:综合题
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { name: 'Tom', foo1: function() { console.log(this.name) }, foo2: () => { console.log(this.name) }, foo3: function() { return () => { console.log(this.name) } } }
obj.foo1() obj.foo2() obj.foo3()()
|
5. 手写实现(核心代码)
手写call
1 2 3 4 5 6 7 8
| Function.prototype.myCall = function(context, ...args) { context = context || window const fn = Symbol('fn') context[fn] = this const result = context[fn](...args) delete context[fn] return result }
|
手写apply
1 2 3 4 5 6 7 8
| Function.prototype.myApply = function(context, args = []) { context = context || window const fn = Symbol('fn') context[fn] = this const result = context[fn](...args) delete context[fn] return result }
|
手写bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Function.prototype.myBind = function(context, ...args1) { const self = this const fBound = function(...args2) { return self.apply( this instanceof fBound ? this : context, [...args1, ...args2] ) } if (this.prototype) { fBound.prototype = Object.create(this.prototype) } return fBound }
|
6. 面试口述版本
Q: 请解释JavaScript中this的指向规则
“JavaScript中的this是函数运行时的上下文对象,指向取决于调用方式。
主要有五种绑定规则,按优先级从高到低:
第一,new绑定。使用new调用时,this指向新创建的对象。
第二,显式绑定。通过call、apply、bind显式指定this,其中bind会返回硬绑定函数,无法再次改变。
第三,隐式绑定。作为对象方法调用时,this指向该对象。要注意隐式丢失问题,比如赋值给变量或作为回调传递时会丢失this。
第四,默认绑定。独立函数调用时,非严格模式指向全局对象,严格模式是undefined。
第五,箭头函数。没有自己的this,继承外层作用域的this,在定义时确定,无法改变。
实际应用中,回调函数容易丢失this,可以用箭头函数或bind解决。快速判断方法是:看调用时有没有点,有点就是点前面的对象,没点看是否用了call/apply/bind,都没有就是默认绑定,箭头函数直接看外层作用域。”
7. 高频追问(6题)
Q1: 箭头函数和普通函数的this有什么区别?
普通函数: this在调用时确定,可以通过call/apply/bind改变,可以作为构造函数。
箭头函数: this在定义时确定并继承外层作用域,无法改变,不能作为构造函数。
Q2: 为什么bind只能生效一次?
bind返回的是一个新函数,内部已经硬绑定了this。再次bind只是在外面包了一层,但内部的this已经固定无法改变。
Q3: 如何解决回调函数中this丢失?
1 2 3 4 5 6 7 8 9
| setTimeout(() => obj.sayName(), 1000)
setTimeout(obj.sayName.bind(obj), 1000)
const self = obj setTimeout(function() { self.sayName() }, 1000)
|
Q4: class中的this指向?
1 2 3 4 5 6 7 8 9 10 11
| class Person { sayName() { console.log(this.name) } sayAge = () => { console.log(this.name) } }
const p = new Person('Tom') const fn1 = p.sayName fn1()
const fn2 = p.sayAge fn2()
|
Q5: 事件处理函数中的this?
1 2 3 4 5 6 7 8
| button.addEventListener('click', function() { console.log(this) })
button.addEventListener('click', () => { console.log(this) })
|
Q6: 严格模式对this有什么影响?
非严格模式下,独立调用的函数this指向全局对象。严格模式下,this是undefined,且不会自动转换为全局对象。
8. 实战经验(3个典型场景)
场景1:React组件中的this丢失
问题: 事件处理函数中this为undefined
解决:
1 2 3 4 5 6 7 8 9 10 11 12
| class Button extends React.Component { handleClick = () => { this.setState({ count: this.state.count + 1 }) } constructor() { super() this.handleClick = this.handleClick.bind(this) } }
|
场景2:定时器中的this
问题: setTimeout回调中this指向全局
解决:
1 2 3 4 5 6 7 8 9
| const obj = { name: 'Tom', delayedSay() { setTimeout(() => { console.log(this.name) }, 1000) } }
|
场景3:数组方法回调中的this
问题: forEach/map等回调中this丢失
解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = { name: 'Tom', list: [1, 2, 3], printList() { this.list.forEach(item => { console.log(this.name, item) }) this.list.forEach(function(item) { console.log(this.name, item) }, this) } }
|
9. 记忆路线图
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| this指向 ├── 快速判断:普通函数看调用者,箭头函数看定义处 ├── 五种绑定(优先级) │ ├── new绑定 → 新对象 │ ├── 显式绑定 → call/apply/bind │ ├── 隐式绑定 → obj.fn() │ ├── 默认绑定 → window/undefined │ └── 箭头函数 → 继承外层 ├── 常见陷阱 │ ├── 隐式丢失(赋值、回调) │ └── 箭头函数无法改变this └── 解决方案 ├── 箭头函数 └── bind绑定
|
10. 总结
核心要点:
- this指向取决于调用方式,不是定义方式
- 优先级:new > 显式绑定 > 隐式绑定 > 默认绑定
- 箭头函数继承外层this,无法改变
- 回调函数易丢失this,用箭头函数或bind解决
记忆口诀:
- 普通函数看调用者,箭头函数看定义处
- new最优先,bind强绑定
- 对象方法看点前,独立调用看模式