JavaScript 基础心法——this
Category(分类): JavaScript Status: 已整理(2026)
原文只有标题和两个来源链接。本文保留来源,并补回
this的判断规则、严格模式、箭头函数、class、浏览器事件和不同模块环境的现代说明。
一、先记住一句话
对普通函数来说,this 通常由调用方式决定,而不是由函数定义位置决定。阅读一段代码时,先看函数是如何被调用的,再判断 this。
箭头函数是重要例外:箭头函数没有自己的 this,它从定义位置的词法环境捕获 this,因此不能通过 call、apply 或 bind 改变自己的 this。
const object = {
value: 10,
normal() {
return this.value
},
arrow: () => this?.value
}
console.log(object.normal()) // 10
console.log(object.arrow()) // 通常是 undefined:箭头函数不从调用点取 this
上面的箭头函数还受到所在脚本/模块环境影响,因此不要用它来定义需要动态接收者的方法。
二、普通函数的四种常见绑定
对普通、非箭头函数,可以先按下面的顺序排查:
new绑定:用new调用构造函数时,创建的新实例成为this;- 显式绑定:
call、apply或bind指定接收者;但new调用绑定函数时会优先创建新实例; - 隐式绑定:以
object.method()形式调用,object是this; - 默认绑定:独立调用普通函数,严格模式下是
undefined,非严格模式下会替换为该运行时的全局对象。
这是学习时很有用的排查顺序,但 Proxy、Symbol.hasInstance、宿主回调和箭头函数等特殊机制仍需单独看规则。
2.1 默认绑定与严格模式
function sloppy() {
return this
}
function strictFunction() {
'use strict'
return this
}
console.log(sloppy() === globalThis) // true:非严格普通函数的典型结果
console.log(strictFunction()) // undefined
不要把“独立调用时 this 是 window”当成普遍结论:
- 浏览器经典脚本中的非严格普通函数通常会得到
window,它也是该环境的globalThis; - ES module 和 class 方法默认严格,独立调用时通常是
undefined; - Node.js CommonJS 顶层的
this与浏览器顶层不同; - Worker、Node.js ESM、iframe 和其他 Realm 也有各自的全局对象。
需要表示当前运行时全局对象时,优先使用标准的 globalThis,但不要用它伪造严格函数的 this 语义。
2.2 隐式绑定
const user = {
name: 'Ada',
getName() {
return this.name
}
}
console.log(user.getName()) // Ada
const getName = user.getName
console.log(getName()) // 非严格函数可能是 undefined 或读取全局对象属性
把方法取出来再调用,会丢失原来的接收者。可以使用 bind,或显式传递对象:
const boundGetName = user.getName.bind(user)
console.log(boundGetName()) // Ada
console.log(user.getName.call(user)) // Ada
括号本身通常不会改变接收者,但逗号运算符、赋值、解构和回调传递可能会造成丢失:
const object = {
value: 1,
getValue() {
return this.value
}
}
console.log((object.getValue)()) // 1
console.log((0, object.getValue)()) // 非严格函数下通常是 undefined
2.3 显式绑定:call、apply 和 bind
function describe(prefix, suffix) {
return `${prefix}${this.name}${suffix}`
}
const person = { name: 'Ada' }
console.log(describe.call(person, 'Hello ', '!'))
console.log(describe.apply(person, ['Hello ', '!']))
const boundDescribe = describe.bind(person, 'Hello ')
console.log(boundDescribe('!'))
call(thisArg, arg1, arg2, ...)立即调用;apply(thisArg, argsArray)立即调用,把参数作为 array-like 对象传入;bind(thisArg, ...args)返回一个新函数,不会立即调用,还可以预绑定部分参数。
call、apply 和 bind 对箭头函数的 this 不起重新绑定作用:
const arrow = () => this
const receiver = { value: 1 }
console.log(arrow.call(receiver) === arrow()) // true:箭头函数仍使用词法 this
2.4 new 绑定
function User(name) {
this.name = name
}
const user = new User('Ada')
console.log(user.name) // Ada
console.log(user instanceof User) // true
new User() 会创建对象、把对象的原型关联到 User.prototype、以该对象作为 this 执行构造函数,并返回对象(构造函数显式返回对象时还有额外规则)。
绑定函数也可以被 new 调用:这时新实例优先于绑定的 this,但通过 bind 预绑定的参数仍会保留。
三、严格模式、脚本和模块环境
this 的结果还取决于代码运行环境:
| 环境 | 顶层 this 的常见结果 | 注意 |
|---|---|---|
| 浏览器经典脚本 | 通常是 globalThis/window | 仅指脚本顶层,不代表所有函数调用 |
| 浏览器 ES module | undefined | 模块默认严格模式 |
| Node.js CommonJS | 通常是 module.exports | 文件被 CommonJS 包装函数包裹 |
| Node.js ES module | undefined | ESM 顶层没有 CommonJS 的 this |
| class 方法 | 由调用方式决定 | class 方法默认严格模式 |
不要用一个浏览器控制台的结果去推导 Nuxt SSR、Node.js 模块或 Web Worker 的 this。如果代码需要跨宿主运行,应减少对顶层 this 的依赖,使用显式参数、模块导入或 globalThis。
四、箭头函数的词法 this
箭头函数从外层词法环境捕获 this,没有自己的 arguments、super 和 new.target 绑定,也不能作为构造函数使用:
const counter = {
value: 0,
incrementLater() {
setTimeout(() => {
this.value += 1
console.log(this.value)
}, 0)
}
}
counter.incrementLater()
如果把定时器回调写成普通函数,它的 this 就不再自动指向 counter,除非显式绑定:
const counter = {
value: 0,
incrementLater() {
setTimeout(function () {
this.value += 1
}.bind(this), 0)
}
}
类字段箭头函数可以捕获实例,但会为每个实例创建一个函数,是否使用应结合内存和传递回调的需要决定:
class ButtonController {
count = 0
handleClick = () => {
this.count += 1
}
}
五、DOM 事件中的 this
使用普通函数作为 DOM 事件监听器时,浏览器通常把 this 设置为 event.currentTarget;箭头函数不会获得这个动态绑定:
const button = document.querySelector('#save')
function handleClick(event) {
console.log(this === event.currentTarget) // true
}
button.addEventListener('click', handleClick)
button.removeEventListener('click', handleClick)
箭头函数中应使用事件对象:
button.addEventListener('click', event => {
console.log(event.currentTarget)
})
removeEventListener 需要同一个函数引用和匹配的监听选项,因此不要在添加和移除时分别创建两个匿名函数。组件框架中还应在组件卸载时清理监听器。
六、class 方法不会自动绑定实例
class Person {
constructor(name) {
this.name = name
}
greet() {
return `Hello, ${this.name}`
}
}
const person = new Person('Ada')
console.log(person.greet()) // Hello, Ada
const greet = person.greet
// greet():严格模式下 this 是 undefined,会访问失败
const boundGreet = person.greet.bind(person)
console.log(boundGreet()) // Hello, Ada
把 class 方法传给事件监听器、定时器或第三方库时,必须根据 API 约定处理接收者。可选方式包括:
- 在构造函数中
this.greet = this.greet.bind(this); - 使用类字段箭头函数;
- 在回调中显式调用
person.greet(); - 更推荐在纯逻辑中把依赖作为参数传递。
七、常见丢失 this 的场景
7.1 解构方法
const api = {
baseUrl: 'https://example.com',
getUrl(path) {
return `${this.baseUrl}${path}`
}
}
const { getUrl } = api
// getUrl('/users'):丢失 api 这个接收者
const getApiUrl = api.getUrl.bind(api)
console.log(getApiUrl('/users')) // https://example.com/users
7.2 传递给数组方法或第三方 API
数组的 map、forEach 等方法不会自动把外层对象作为回调的 this:
const formatter = {
prefix: '#',
format(value) {
return `${this.prefix}${value}`
}
}
const values = [1, 2]
console.log(values.map(formatter.format, formatter)) // ['#1', '#2']
这里是数组方法显式提供了 thisArg;现代代码也可以用箭头函数显式捕获:
console.log(values.map(value => formatter.format(value)))
八、检查 this 的最小实验
function showThis(label) {
'use strict'
console.log(label, this)
}
const object = { value: 1 }
showThis('default') // undefined
showThis.call(object, 'call') // { value: 1 }
showThis.apply(object, ['apply']) // { value: 1 }
const bound = showThis.bind(object)
bound('bind') // { value: 1 }
严格模式示例可以清楚展示原始 thisArg。非严格函数对 null/undefined 和原始值的替换或装箱行为是另一条规则,不应写成“call(null) 永远指向 window”。
九、总结
- 普通函数的
this主要由调用形式决定; - 箭头函数的
this由定义位置词法捕获,不能重新绑定; new调用、显式绑定、隐式绑定、默认绑定有明确的排查顺序;call/apply立即调用,bind返回新函数;- 严格模式、ES module、class、Node.js 和浏览器经典脚本的默认行为不同;
- 方法解构、回调传递和事件监听器是最常见的
this丢失场景; - 业务代码可以优先使用显式参数和稳定的闭包,减少对隐式
this的依赖。