小白的JS学习之路(六)—— “this”
学习笔记整理,从"为什么要有 this"到五条绑定规则,再到箭头函数,一次搞懂 JavaScript 中 this 的所有场景。
前言
上一篇文章我们聊了原型,这期来聊一个让无数新手头疼的概念——this。
很多同学学 this 的时候,状态是这样的:
"这个函数里的 this 到底是谁?" "等等,这个也没调用啊,this 怎么变了?" "箭头函数又没有 this,那怎么知道指向谁?"
别急,我们从为什么要有 this 开始,一步一步理清楚。
一、为什么要有 this?
先思考一个问题:假如没有 this,代码会长什么样?
// 没有 this 的写法(繁琐)
function identify(context) {
return context.name.toUpperCase()
}
function speek(context) {
var greeting = 'hello, I am ' + identify(context)
console.log(greeting)
}
var me = { name: 'tom' }
speek(me)
每个函数都要显式传入对象,才能访问那个对象的属性。
有了 this 呢?
// 有 this 的写法(简洁优雅)
function identify() {
return this.name.toUpperCase()
}
function speek() {
var greeting = 'hello, I am ' + identify.call(this)
console.log(greeting)
}
var me = { name: 'tom' }
speek.call(me) // 直接把 me 和 this 关联起来
this 解决的问题: 提供了一种更优雅的方式隐式传递对象的引用,让代码更简洁、更易于复用。
二、this 可以出现在哪?
2.1 全局 this === Window
在浏览器环境下,直接输出 this:
console.log(this) // Window {}
2.2 函数体内的 this
this 在函数体内出现时,具体指向取决于函数的调用方式,不是固定不变的。
注意:在 Node.js 环境下输出
this,得到的是空对象{},这是因为 Node.js 复用了 V8 引擎但做了不同处理。
三、五条绑定规则
这是本章的核心。this 到底指向谁,取决于函数的调用方式。
规则一:默认绑定——指向 Window
当函数独立调用时(没有任何对象引导),this 默认指向 Window。
var a = 1 // === window: { a: 1 }
function foo() {
console.log(this.a) // 独立调用 → this === Window
}
function bar() {
var a = 2
foo() // 虽然 bar 被调用了,但 foo 是独立调用的
console.log(this) // Window
}
bar() // bar 本身也是独立调用的
// 输出:
// 1 ← foo 里 this.a === Window.a === 1
// Window {}
什么是独立调用?
function foo() {}
foo() // ✅ 独立调用
test = {
var a = 2
foo()
}
test.foo() // ❌ 不是独立调用(被 test 引导)
规则二:隐式绑定——指向拥有它的对象
当一个函数被一个上下文对象所拥有并被该对象调用时,this 指向这个对象。
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo // 把 foo 函数赋值给 obj
}
obj.foo() // 被 obj 调用 → this === obj
// 输出:1
规则三:隐式丢失——指向最近的对象
当一个函数被多层对象调用时,this 指向最近的那个对象。
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo // 第一层引用
}
var oo = {
a: 2,
foo: obj.foo // 第二层引用(把 obj.foo 再赋值给 oo.foo)
}
oo.foo.foo() // foo 被 oo 调用 → this === oo
// 输出:2
oo.foo是obj.foo,再调用oo.foo.foo(),实际还是调foo,但这次被oo引导,this就指向oo了。
规则四:显式绑定——强行指定 this
有些时候函数是独立调用的,this 指向 Window,但我们想强行指定它指向某个对象,怎么办?
用 call、apply、bind。
4.1 call——零散传参
function foo(x, y) {
console.log(this.a, x + y)
}
var fu = { a: 3 }
foo.call(fu, 1, 2) // 强行把 this 指向 fu
// 输出:3 3
4.2 apply——数组传参
foo.apply(fu, [2, 3]) // 参数用数组集中传入
// 输出:3 5
| 方法 | 参数形式 | 示例 |
|---|---|---|
call | 零散参数 | foo.call(obj, 1, 2) |
apply | 数组参数 | foo.apply(obj, [1, 2]) |
bind | 预绑定+调用 | foo.bind(obj, 1, 2)() |
4.3 bind——预绑定,延迟调用
var fu = { a: 3 }
var bound = foo.bind(fu, 1, 3)
bound() // 输出:3 4
注意:
bind传参可以放在前面(bind 时),也可以放在后面(调用时):foo.bind(fu, 1, 3)() // 参数在 bind 里 foo.bind(fu)(1, 3) // 参数在调用时 foo.bind(fu, 1)(3) // 参数两边各放一点
规则五:new 绑定——指向实例对象
当用 new 调用函数时,this 会指向新创建的实例对象。
function Car(name) {
this.name = name
}
var car1 = new Car('SU7')
var car2 = new Car('蔚来')
console.log(car1.name) // "SU7"
console.log(car2.name) // "蔚来"
这背后的原理是:
new内部用.call(this)把函数的this强行绑定到新对象上。
四、箭头函数没有 this
这是 ES6 引入的一个重要特性。
4.1 箭头函数不绑定 this
箭头函数没有自己的 this。写在箭头函数里的 this,实际上是它外层第一个非箭头函数的 this。
function outer() {
const inner = () => {
console.log(this) // 继承外层 outer 的 this
}
inner()
}
const obj = { name: 'tom' }
outer.call(obj) // outer 的 this 被 call 指向 obj
// 输出:{ name: 'tom' }
4.2 箭头函数不能用 new 调用
因为箭头函数没有自己的 this,自然也没有构造能力:
const Foo = () => {}
// new Foo() // ❌ 报错:Foo is not a constructor
五、综合对比:五条规则一句话总结
| 规则 | 调用方式 | this 指向 |
|---|---|---|
| 默认绑定 | 独立调用 foo() | Window |
| 隐式绑定 | 被对象调用 obj.foo() | obj |
| 隐式丢失 | 被多层对象调用 oo.foo.foo() | 最近的那个对象 |
| 显式绑定 | call / apply / bind | 手动指定的对象 |
| new 绑定 | new 函数() | 新创建的实例 |
六、一个综合练习
用学到的知识来分析下面这段代码的输出:
var a = 1
function bar() {
var a = 2
function foo() {
console.log(this.a)
}
foo() // 独立调用 → this === Window → Window.a === 1
}
bar()
// 输出:1
分析:虽然 foo 在 bar 内部被调用,但 foo() 是独立调用(没有任何对象引导),所以 this 指向 Window,输出 1。
七、总结
- 为什么要有 this —— 隐式传递对象引用,代码更简洁
- this 出现在哪 —— 全局、函数体内,指向取决于调用方式
- 五条绑定规则 ——
- 默认绑定 → Window(独立调用)
- 隐式绑定 → 对象(被对象调用)
- 隐式丢失 → 最近对象(多层引用)
- 显式绑定 → 指定对象(call/apply/bind)
- new 绑定 → 实例对象(new 操作符)
- 箭头函数 ——
- 没有自己的
this - 继承外层函数的
this - 不能用
new调用
- 没有自己的
一句话总结 this:
this 指向谁,不由函数写在哪决定,而由函数怎么调用决定。
写在最后
this 是 JavaScript 中最"玄学"的概念之一,核心就是一句话:看调用方式,不看写法。记住五条规则,理解箭头函数的"继承"逻辑,遇到 this 题目就不会再慌了。
如果这篇文章对你有帮助,欢迎点赞收藏,我们下期见!👋
🔗 系列文章: