JavaScript 的 this 到底指向谁?

开头:一个让无数人困惑的问题

js 复制代码
const obj = {
    name: '张三',
    sayHi() {
        console.log(`你好,我是 ${this.name}`);
    }
};

obj.sayHi();           // "你好,我是 张三" ✅ 符合直觉

const fn = obj.sayHi;
fn();                  // "你好,我是 undefined" ❌ 怎么回事?

同样的函数,换一种调用方式,结果完全不同。这就是 JavaScript 的 this------一个让初学者抓狂、让面试官兴奋的话题

本文用一个统一的心智模型,把 this 的所有场景串起来。读完你会发现,this 其实很简单。


一、核心心法:一句话记住所有场景

this 是运行时绑定。谁调用函数,this 就指向谁。函数定义在哪里不重要,重要的是谁在"点"它。

记住这句话,你已经理解了 this 的 80%。剩下的 20% 是五个具体场景的细节。


二、五种场景,一张优先级表搞定

this 的所有行为可以归纳为五条规则,而且它们的优先级是确定的

sql 复制代码
new 绑定  >  call/apply/bind  >  对象方法调用  >  普通调用
优先级 场景 this 指向 怎么写
🔴 最高 new 构造调用 新创建的实例 new Foo()
🟠 显式绑定 你指定的对象 fn.call(obj)
🟡 方法调用 点号前面的对象 obj.fn()
🟢 最低 普通调用 window / undefined fn()

遇到任何 this 问题,从上到下对号入座,优先级最高的那条规则生效。


三、逐条拆解

🔴 规则一:new 绑定(最高优先级)

js 复制代码
function Person(name) {
    this.name = name;  // this 是谁?别急,往下看
}

const p = new Person('张三');
console.log(p.name);  // "张三"

new 操作符在背后做了四件事:

markdown 复制代码
1. 创建一个空对象           →  {}
2. 空对象挂上原型链         →  {} 的 __proto__ 指向 Person.prototype
3. 执行 Person,this 绑定空对象  →  Person.apply({}, args)  ← 这是关键
4. 返回这个对象

手动还原一下,你会看得更清楚:

js 复制代码
function myNew(Constructor, ...args) {
    const obj = {};                                    // 步骤 1
    Object.setPrototypeOf(obj, Constructor.prototype); // 步骤 2
    const result = Constructor.apply(obj, args);       // 步骤 3 ← this 在这里绑定
    return result instanceof Object ? result : obj;    // 步骤 4
}

第三步就是答案:new 本质上也是用 applythis 强行指到新对象上


🟠 规则二:显式绑定(call / apply / bind)

当你想让 this 指向一个特定的对象,用这三兄弟:

js 复制代码
function sayHi() {
    console.log(`你好,我是 ${this.name}`);
}

const a = { name: '张三' };
const b = { name: '李四' };

sayHi.call(a);    // "你好,我是 张三"  --- 逐个传参,立刻执行
sayHi.apply(b);   // "你好,我是 李四"  --- 数组传参,立刻执行

const fn = sayHi.bind(a);
fn();             // "你好,我是 张三"  --- 返回新函数,不立即执行

callapply 的区别只是传参方式不同,bind 的区别是不立即执行 ,返回一个新函数------这就是为什么事件监听场景只能用 bind

js 复制代码
// ❌ call 立即执行了,不行
element.addEventListener('click', obj.handle.call(obj));

// ✅ bind 返回新函数,事件触发时才执行,可以
element.addEventListener('click', obj.handle.bind(obj));

方法借用的威力:

同一个函数,被不同的对象"借用",表现出完全不同的行为------不需要任何继承关系,这是 JavaScript 独有的灵活性:

js 复制代码
function introduce() {
    console.log(`我叫 ${this.name},来自 ${this.city}`);
}

const p1 = { name: '张三', city: '北京' };
const p2 = { name: '李四', city: '上海' };

introduce.call(p1);  // "我叫 张三,来自 北京"
introduce.call(p2);  // "我叫 李四,来自 上海"

🟡 规则三:方法调用(隐式绑定)

对象的方法被调用,this 指向调用它的对象:

js 复制代码
const obj = {
    name: '张三',
    sayHi() {
        console.log(this.name);
    }
};

obj.sayHi();  // this → obj,输出 "张三"

这个看起来最简单,但藏了一个高频面试题------隐式丢失:

js 复制代码
// 把方法赋值给一个变量
const fn = obj.sayHi;
fn();  // this → window(严格模式 undefined),输出 undefined

为什么?因为你把函数从这个对象里"掏出来"了,fn() 是裸调用,没有 obj. 前缀。函数本质上是独立的值obj.sayHi 只是把那个函数值取出来,取出来之后它就跟 obj 没有任何关系了。

一句话:this 看的是调用时有没有点号,不是定义时在不在对象里。


🟢 规则四:普通调用(默认绑定)

没有任何修饰的裸函数调用,this 指向全局对象:

js 复制代码
function foo() {
    console.log(this);
}

foo();  // 浏览器 → window  |  Node.js → global  |  严格模式 → undefined

var 声明的变量会挂到 window 上,这是 JavaScript 早期的一个设计缺陷。letconst 修复了这个问题,所以现在很少有人用 var 了。


🔵 规则五:事件处理函数

js 复制代码
button.addEventListener('click', function(e) {
    console.log(this);  // this → 触发事件的 button 元素
});

事件监听器里,this 默认指向触发事件的 DOM 元素。但这经常不是你想要的------你需要 this 指向你自己的数据对象:

js 复制代码
const user = {
    name: '张三',
    score: 0,
    handleClick() {
        this.score += 10;
        console.log(`${this.name} 当前分数:${this.score}`);
    }
};

// ❌ this → button 元素,this.score = undefined,this.name = undefined
button.addEventListener('click', user.handleClick);

// ✅ bind 锁死 this,this → user,正常累加分数
button.addEventListener('click', user.handleClick.bind(user));

这是 bind 在实际开发中最高频的应用场景。


四、箭头函数:规则之外的特殊存在

上面的规则都适用普通 function。箭头函数跳出这个体系 ------它根本没有自己的 this

js 复制代码
const obj = {
    name: '张三',

    // 普通函数
    regular() {
        setTimeout(function () {
            console.log(this.name); // this → window ❌
        }, 100);
    },

    // 箭头函数
    arrow() {
        setTimeout(() => {
            console.log(this.name); // this → obj ✅
        }, 100);
    }
};
普通 function 箭头函数 () => {}
自己的 this ✅ 有,运行时动态绑定 ❌ 没有,从外层捕获
适用场景 构造函数、需要动态 this 回调、需要固定 this
能被 new 调用? ✅ 可以 ❌ 不行

设计初衷 :在箭头函数出现之前,每次写回调都要 var self = this 或者 .bind(this),非常繁琐。箭头函数就是为这个痛点而生。


五、终极速查:遇到 this 问题怎么排查?

按这个顺序排查,三步定位:

kotlin 复制代码
1. 是箭头函数吗?
   → 是:this = 定义时外层作用域的 this,不用往下看了
   → 否:继续

2. 是 new 调用的吗?(前面有 new 关键字)
   → 是:this = 新创建的实例对象
   → 否:继续

3. 函数怎么被调用的?
   → fn.call(obj) / fn.apply(obj) / fn.bind(obj)() → this = obj
   → obj.fn()   → this = obj(点号前面的那个)
   → fn()       → this = window / undefined

六、经典陷阱一览

陷阱 代码 原因 解法
隐式丢失 const f = obj.m; f(); 脱离了对象,变成普通调用 .bind(obj) 或用箭头函数包裹
定时器丢 this setTimeout(obj.m, 100) 回调被独立调用 () => obj.m()obj.m.bind(obj)
事件监听丢 this el.addEventListener('click', obj.m) this 变成了 DOM 元素 obj.m.bind(obj)
class 方法提取 const h = new Btn().click; h(); class 默认严格模式 constructor 中 bind 或用箭头字段
箭头函数冒充构造函数 new ArrowFn() 箭头函数没自己的 this 换普通 function

结尾

this 表面看起来有很多特殊情况,但底层逻辑只有一条:谁调用,就指向谁

这种设计是 JavaScript 的"基因"决定的------它不是基于类的语言,函数是一等公民,方法可以被任意对象借用。这种灵活性是代价,也是威力。

把那张优先级表记在心里,以后遇到任何 this 的问题,从上往下对号入座就好。


如果这篇文章对你有帮助,欢迎分享给同样被 this 困扰的朋友。

相关推荐
触底反弹1 小时前
🔥 2026 年爆火的 Harness Engineering 到底是什么?从原理到实战一文讲透
javascript·人工智能·程序员
mONESY1 小时前
一文搞定JavaScript不同场景中 this 的指向问题
javascript
用户298698530141 小时前
在 React 中使用 JavaScript 合并 Excel 文件
前端·javascript·react.js
烬羽1 小时前
面试官:聊聊 LocalStorage 和 this 指向?看这篇就够了
面试·程序员
大流星1 小时前
LangChainJs之基础模型(一)
javascript·langchain
橘子星1 小时前
JavaScript this 指向全解实战指南
前端·javascript
weedsfly1 小时前
JS垃圾回收:从原理到项目实战,彻底根治内存泄漏
前端·javascript·面试
万少13 小时前
万少的博客 - 技术分享与解决方案
前端·javascript·后端