在 JavaScript 中,this 指向堪称「千古难题」🤯—— 明明是同一个函数,换种调用方式,this 就像脱缰的野马四处乱窜。今天我们结合具体代码,从基础到进阶,掰开揉碎讲透 this 指向的所有细节,保证看完让你豁然开朗!🚀
一、this 指向的核心原则:谁调用,指向谁!
先记住一句话:this 的指向由「函数被调用的方式」决定,而非定义的位置。就是:
函数调用时,自动生成的对象,函数的执行方式决定,指向最后调用它的对象。
二、不同场景下的 this 指向(附代码逐行解析)
2.1 普通函数调用:this 指向全局对象(非严格模式)
代码示例 :
var name = '小卢'; // 全局变量,挂载在 window 上
function fn() {
var name = '小朱'; // 函数内部变量,与 this 无关
console.log(this.name); // 打印 this 指向的对象的 name 属性
}
// 场景1:直接调用函数
fn(); // 输出:'小卢'
// 原理:全局环境下调用 fn,相当于 window.fn(),this 指向 window
// 场景2:通过 window 调用(等价于场景1)
window.fn(); // 输出:'小卢'
// 原理:显式通过 window 调用,this 仍指向 window
💡 细节补充:
- 若开启严格模式(
"use strict"),全局调用函数时this会指向undefined,而非window。 - 函数内部的
var name = '小朱'是局部变量,和this.name无关,this只认「调用对象的属性」。
2.2 对象方法调用:this 指向调用方法的对象
代码示例 :
let obj = {
name: '蔡徐坤',
fn: function() {
console.log(this.name);
}
};
// 场景3:通过对象调用方法
obj.fn(); // 输出:'蔡徐坤'
// 原理:obj 是调用者,this 指向 obj
// 场景4:方法被赋值后调用(this 丢失)
const fn2 = obj.fn; // 仅赋值函数本身,脱离 obj 上下文
fn2(); // 输出:'小卢'
// 原理:fn2 相当于全局调用,this 指向 window(同场景1)
😱 注意:一旦函数脱离原对象单独调用,this 会「回归」全局对象(非严格模式),这就是常见的「this 丢失问题」。
2.3 构造函数调用:this 指向新创建的实例对象
代码示例 :
function Person(name, age) {
// 当用 new 调用时,this 指向新创建的空对象 {}
this.name = name; // 给新对象添加 name 属性
this.age = age; // 给新对象添加 age 属性
}
// 给构造函数的原型添加方法(所有实例共享)
Person.prototype = {
sayHi: function() {
console.log(`你好,我是${this.name}`); // this 指向调用者(实例)
}
};
// 用 new 关键字调用构造函数
const haha = new Person('haha', 18);
// 此时:haha 就是 Person 内部 this 指向的新对象
console.log(haha.__proto__ === Person.prototype); // 输出:true
// 原理:new 会让实例的 __proto__ 指向构造函数的 prototype
haha.sayHi(); // 输出:'你好,我是haha'
// 原理:sayHi 中的 this 指向调用者 haha(实例对象)
✨ 关键细节:
new关键字的作用:创建空对象 → 绑定this到空对象 → 执行构造函数 → 返回新对象。- 原型方法中的
this仍遵循「谁调用指向谁」,这里haha调用sayHi,所以this指向haha。
2.4 事件处理函数:this 指向触发事件的元素
代码示例 :
<button id="btn">点击</button>
<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', function() {
console.log(this); // 输出:<button id="btn">点击</button>
});
</script>
📌 原理:DOM 事件回调函数中,this 默认指向「触发事件的 DOM 元素」(即 btn)。
三、改变 this 指向的三大利器:call/apply/bind
当默认的 this 指向不符合需求时,我们可以用这三个方法强行修改!它们位于函数原型链上(Function.prototype),作用是「手动绑定函数执行时的 this」。
3.1 call:立即执行,参数逐个传递
语法:函数.call(新this指向, 参数1, 参数2, ...)
3.2 apply:立即执行,参数以数组传递
语法:函数.apply(新this指向, [参数1, 参数2, ...])
3.3 bind:返回新函数(延迟执行),参数预先绑定
语法:const 新函数 = 函数.bind(新this指向, 参数1, 参数2, ...)
(调用新函数时才执行,this 固定为绑定的值)
代码示例 :
var a = {
name: '小心心',
fn: function(a, b) {
console.log(this.name); // 依赖 this 指向
console.log(a, b); // 接收参数
}
};
const b = a.fn; // b 是脱离 a 的函数,默认 this 指向 window
// 用 call 改变 this 并执行
b.call(a, 1, 2);
// 输出:'小心心' 、1 2
// 解析:this 被改为 a,参数 1、2 逐个传递
// 用 apply 改变 this 并执行
b.apply(a, [1, 2]);
// 输出:'小心心' 、1 2
// 解析:this 被改为 a,参数以数组 [1,2] 传递
// 用 bind 改变 this 并返回新函数
const func2 = b.bind(a, 1, 2);
func2(); // 调用新函数才执行
// 输出:'小心心' 、1 2
// 解析:bind 不立即执行,而是返回一个绑定了 this 和参数的新函数
🔍 区别总结:
| 方法 | 执行时机 | 参数传递方式 | 返回值 |
|---|---|---|---|
call | 立即执行 | 逗号分隔 | 函数执行结果 |
apply | 立即执行 | 数组或类数组 | 函数执行结果 |
bind | 延迟执行 | 逗号分隔(可预填) | 新函数 |
四、实战中的 this 丢失问题及解决
在实际开发中,最容易踩坑的就是「this 丢失」,比如在事件回调、定时器中。我们以 button.js 为例,看看如何解决!
问题场景:事件回调中 this 指向错误
原始问题代码:
function Button(id) {
this.element = document.querySelector(`#${id}`);
this.bindEvent();
}
Button.prototype.bindEvent = function() {
// 给按钮绑定点击事件
this.element.addEventListener('click', function() {
// 这里的 this 指向触发事件的按钮(DOM元素)
this.element.style.background = 'red'; // 报错!因为按钮没有 element 属性
});
};
❌ 报错原因:回调函数中的 this 是按钮元素,此时 this => this.element ,而我们需要 this 指向 Button 实例(才能访问 this.element)。
解决方法 1:用 bind 强行绑定 this
原来this => this.element通过 bind => this
Button.prototype.bindEvent = function() {
// 用 bind(this) 将回调中的 this 绑定为 Button 实例
this.element.addEventListener('click', this.setBgColor.bind(this));
};
Button.prototype.setBgColor = function() {
this.element.style.background = '#1abc9c'; // 正确!this 指向 Button 实例
};
解决方法 2:用箭头函数(继承外层 this)
箭头函数没有自己的 this,会继承外层作用域的 this
Button.prototype.bindEvent = function() {
// 箭头函数的 this 继承自 bindEvent 中的 this(即 Button 实例)
this.element.addEventListener('click', () => {
this.element.style.background = '#1abc9c'; // 正确!
});
};
解决方法 3:直接不要element
由于this => this.element ,不要element直接解决
Button.prototype.bindEvent = function() {
this.element.addEventListener('click', this.setBgColor);
};
Button.prototype.setBgColor = function() {
this.style.background = '#1abc9c'; // this指向this.element
};
五、箭头函数与 this:特殊的存在
箭头函数是个「异类」—— 它没有自己的 this,永远继承外层作用域的 this,且无法通过 call/apply/bind 修改!
(代码示例) :
var a = {
name: 'JJJ',
func1: function() { console.log(this.name); },
func2: function() {
// 箭头函数继承 func2 中的 this(即 a)
setTimeout(() => {
this.func1(); // 输出:'JJJ'(this 指向 a)
}, 1000);
}
};
a.func2();
⚠️ 注意:箭头函数不适合做构造函数(不能用 new 调用),也没有 arguments 对象。
六、总结:this 指向速查表 📝
| 函数调用方式 | this 指向 | 示例代码 |
|---|---|---|
| 普通函数调用 | 全局对象(非严格模式)/ undefined | fn() |
| 对象方法调用 | 调用方法的对象 | obj.fn() |
| 构造函数调用 | 新创建的实例对象 | new Person() |
| 事件处理函数 | 触发事件的 DOM 元素 | btn.onclick = function(){} |
call/apply 调用 | 第一个参数指定的对象 | fn.call(obj) |
bind 返回的函数 | 绑定的对象(固定不变) | const newFn = fn.bind(obj) |
| 箭头函数 | 继承外层作用域的 this | () => { ... } |
掌握 this 指向的关键,在于「关注函数被谁调用」。遇到复杂场景时,不妨在函数中打印 console.log(this) 调试,多写多练自然就熟啦!💪