今天我们来聊一聊 JavaScript 中一个既基础又让人头疼的概念------this。
一、this 是什么?
简单来说,this 是函数执行时内部自动生成的一个对象,它指向调用该函数的上下文。你可以把它理解为函数内部的"环境变量",代表了当前函数运行时所处的对象。
一个形象的比喻
想象一下,你有一个"自我介绍"的功能,不同的人调用它时,"我"这个字指向不同的人:
- 当小明 说"我叫小明",这里的"我"就是小明。
- 当小红 说"我叫小红",这里的"我"就是小红。
在 JavaScript 中,this 就像这句话里的"我",而那个自我介绍的函数就像一句模板:"我叫 xxx"。这个模板里的 this.name 会根据是谁在调用而自动替换成对应的人名。
用代码表示:
javascript
javascript
function introduce() {
console.log(`我叫 ${this.name}`);
}
const ming = { name: '小明', introduce };
const hong = { name: '小红', introduce };
ming.introduce(); // 我叫 小明(this 指向 ming)
hong.introduce(); // 我叫 小红(this 指向 hong)
这里的 introduce 函数内部的 this 就像"我"一样,随着调用者(ming 或 hong)不同,指向也不同。这就是 this 的动态性------它是在函数执行时,根据调用它的对象确定的。
二、this 能做什么?
理解了 this 是动态上下文,那么它能为我们做什么呢?
- 让同一个函数服务于不同的对象,实现代码复用;
- 在构造函数中初始化实例属性;
- 在事件处理中方便地访问触发元素;
- 显式地指定上下文,借用其他对象的方法;
- 在回调函数中优雅地保留外层 this。
下面我们就通过一个个实战场景,来体会 this 的妙用。
三、实战场景一网打尽
场景1:对象方法中的 this ------ 隐式绑定
假设我们有一个用户对象,需要输出用户的名称:
javascript
javascript
const user1 = {
name: '小明',
greet() {
console.log(`大家好,我是 ${this.name}`);
}
};
user1.greet(); // 大家好,我是 小明
当 greet 作为 user1 的方法被调用时,this 指向 user1,所以能正确访问 name。
能做什么:我们可以定义多个类似的对象,使用同一个方法结构,轻松访问各自的数据。
陷阱 :如果把方法赋值给一个变量再调用,this 就会丢失:
javascript
ini
const fn = user1.greet;
fn(); // 大家好,我是 undefined (非严格模式下 this 指向 window,没有 name 属性)
解决方法:使用 bind 强制绑定 this,或者用箭头函数(后面会讲)。
场景2:构造函数中的 this ------ new 绑定
在面向对象编程中,我们经常用构造函数来创建对象:
javascript
javascript
function Person(name, age) {
this.name = name;
this.age = age;
this.intro = function() {
console.log(`我叫 ${this.name},今年 ${this.age} 岁。`);
};
}
const p1 = new Person('小红', 20);
p1.intro(); // 我叫 小红,今年 20 岁。
当使用 new 调用 Person 时,this 指向新创建的空对象,然后我们往这个对象上添加属性,最后返回这个对象。
能做什么:轻松批量创建结构相似的对象,并且每个对象的方法都能正确访问自己的属性。
注意 :如果忘记写 new,this 会指向全局对象,导致全局变量污染。所以构造函数通常首字母大写,提醒自己用 new 调用。
场景3:DOM 事件处理中的 this
在浏览器中处理事件时,this 通常指向触发事件的 DOM 元素:
html
xml
<button id="myBtn">点我</button>
<script>
const btn = document.getElementById('myBtn');
btn.addEventListener('click', function() {
console.log(this); // <button id="myBtn">点我</button>
this.textContent = '已点击';
});
</script>
能做什么 :在事件回调中直接通过 this 操作当前元素,非常方便。
注意 :如果回调使用箭头函数,this 就会指向外层作用域(比如 window),无法直接操作元素。所以事件回调一般用普通函数。
场景4:显式指定 this ------ call / apply / bind
有时候我们需要手动指定函数的 this,比如"借用"其他对象的方法。
javascript
javascript
const user2 = { name: '小刚' };
const user3 = { name: '小丽' };
function introduce(hobby) {
console.log(`我是 ${this.name},喜欢 ${hobby}`);
}
introduce.call(user2, '篮球'); // 我是 小刚,喜欢 篮球
introduce.apply(user3, ['跳舞']); // 我是 小丽,喜欢 跳舞
const introduceXiaoGang = introduce.bind(user2, '足球');
introduceXiaoGang(); // 我是 小刚,喜欢 足球
call和apply立即调用函数,区别是传参方式不同。bind返回一个新函数,永久绑定this,可用于后续调用。
能做什么:实现函数复用,动态改变上下文;也可以用于"函数借用",比如数组方法借用给类数组对象。
场景5:回调函数中保持 this ------ 箭头函数的妙用
在异步回调或定时器中,我们经常需要访问外层的 this,但普通函数的 this 会指向全局(或 undefined 严格模式),导致无法访问期望的对象。
传统解决方式是用 var self = this 缓存,或者用 bind:
javascript
javascript
function Counter() {
this.count = 0;
setInterval(function() {
this.count++; // 这里的 this 指向 window,无法更新 count
console.log(this.count);
}, 1000);
}
new Counter(); // 输出 NaN 或 undefined
用 bind 修正:
javascript
javascript
function Counter() {
this.count = 0;
setInterval(function() {
this.count++;
console.log(this.count);
}.bind(this), 1000);
}
new Counter(); // 1 2 3 ...
而箭头函数让这一切变得简单:箭头函数没有自己的 this,它会捕获定义时外层作用域的 this。
javascript
javascript
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 这里的 this 继承自 Counter 实例
console.log(this.count);
}, 1000);
}
new Counter(); // 1 2 3 ...
能做什么 :在回调、事件监听、Promise 等场景中,优雅地保留外层 this,避免繁琐的 self = this 或 bind。
注意 :箭头函数的 this 一旦确定,就无法通过 call/apply/bind 改变,所以不能用于动态上下文。
场景6:嵌套函数中的 this 问题
在对象方法内部定义普通函数,这个普通函数的 this 会指向全局(或 undefined),这常常让人困惑:
javascript
javascript
const obj = {
name: 'obj',
foo() {
function bar() {
console.log(this.name);
}
bar(); // 非严格模式输出 undefined 或 window.name
}
};
obj.foo();
如何让 bar 也能访问 obj 的 name?有几种方法:
-
用箭头函数(推荐):
javascript
javascriptfoo() { const bar = () => { console.log(this.name); }; bar(); // obj } -
在外层保存
this:javascript
javascriptfoo() { const self = this; function bar() { console.log(self.name); } bar(); } -
用
bind:javascript
javascriptfoo() { function bar() { console.log(this.name); } bar.bind(this)(); }
能做什么:保证嵌套函数也能访问外层对象的属性,避免作用域丢失。
四、this 绑定规则优先级(一句话总结)
当多种规则同时适用时,this 的绑定优先级是:
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定(对象方法) > 默认绑定(独立调用)
箭头函数不参与这个优先级,它完全由外层作用域决定。
五、总结与思考
回到最初的问题:this 能做什么?
- 它让函数灵活地适应不同的调用对象,实现代码复用;
- 它在构造函数中帮助我们初始化实例;
- 它在事件处理中方便操作当前元素;
- 它通过显式绑定让我们能动态指定上下文;
- 它配合箭头函数,优雅地解决了回调中的 this 保持问题。
掌握 this 的关键,不是死记硬背规则,而是在写代码时问自己:这个函数是怎么被调用的? 调用方式决定了 this 的指向。
希望这篇文章能帮你从"this 是什么"的困惑,走向"this 能做什么"的熟练应用。如果你有更多关于 this 的实战经验或疑惑,欢迎在评论区留言讨论!
最后留个思考题:下面代码的输出是什么?为什么?
javascript
ini
const length = 10;
function fn() {
console.log(this.length);
}
const obj = {
length: 5,
method(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
(答案:先输出 10(或 undefined),然后输出 2。因为第一次调用 fn() 是默认绑定,第二次 arguments[0]() 是隐式绑定,this 指向 arguments 对象,其 length 是传入的参数个数,即 2。)
欢迎留言你的答案和理解!我们下期再见。
#前端、#前端面试、#干货
如果这篇这篇文章对您有帮助?关注、点赞、收藏 ,三连支持一下。
有疑问或想法?评论区见。