一、前言
在 JavaScript 的世界里,this
关键字堪称最让人 "爱恨交织" 的特性。对于新手来说,它像一团迷雾,常常让人摸不着头脑,甚至写出各种 "魔幻" 的 bug;但对于老手而言,它又是灵活操控对象上下文的利器。本文将用通俗易懂的语言和丰富的示例,带你从零基础吃透 this
,让你不仅能看懂,更能灵活运用!
二、为什么需要 this
?先看一个没有 this
的糟糕世界
假设我们要实现一个 "打招呼" 的功能,不使用 this
时可能会这样写:
javascript
function greet() {
console.log("Hello, " + name); // 依赖全局变量name
}
const name = "小明";
greet(); // 输出:Hello, 小明
// 当需要问候"小红"时,不得不重新定义函数
const name = "小红";
greet(); // 输出:Hello, 小红(但这会污染全局变量!)
问题显而易见:函数强依赖全局变量,无法复用,且容易引发命名冲突。
this
的救赎:动态绑定上下文
使用 this
后,同一个函数可以适配不同对象:
javascript
function greet() {
console.log("Hello, " + this.name); // this指向调用时的对象
}
const person1 = { name: "小明", greet };
const person2 = { name: "小红", greet };
person1.greet(); // 输出:Hello, 小明
person2.greet(); // 输出:Hello, 小红
核心价值:this
让函数不再依赖固定变量,而是在调用时动态绑定到 "当前对象",极大提升了代码复用性。
三、破除迷思:关于 this
的常见错误认知
在学习 this
之前,必须先破除两个最常见的误解。
误解一:this
指向函数自身
错误代码示例:
scss
function foo() {
this.count++; // 误以为this指向函数foo
}
foo.count = 0; // 给函数添加属性count
for (let i = 0; i < 3; i++) {
foo(); // 每次调用foo时,this指向全局对象(浏览器中是window)
}
console.log(foo.count); // 输出:0(全局变量count被修改,而非函数的count属性)
真相 :
默认情况下,独立调用函数时,this
指向全局对象(浏览器中是 window
,严格模式下为 undefined
)。若想让 this
指向函数自身,需显式绑定:
scss
foo.call(foo); // 强制this指向foo函数
误解二:this
指向函数的作用域
错误代码示例:
scss
function foo() {
const a = 2; // 作用域内的变量a
bar(); // 调用bar函数
}
function bar() {
console.log(this.a); // 输出:undefined(this不查找作用域链)
}
foo();
真相 :
this
的绑定与作用域链完全无关!作用域链是变量查找机制(如查找 a
),而 this
是函数调用时的上下文绑定,两者是独立的概念。
四、this
绑定的四大核心规则
this
的绑定规则共有四条,掌握它们就能破解 99% 的 this
谜题。
规则一:默认绑定(独立函数调用)
适用场景:直接调用函数,没有任何对象 "拥有" 该函数。
scss
function sayHi() {
console.log(this); // 浏览器中输出window,Node.js中输出global
}
sayHi(); // 独立调用,触发默认绑定
严格模式注意:
scss
function strictTest() {
'use strict'; // 开启严格模式
console.log(this); // 输出:undefined
}
strictTest();
规则二:隐式绑定(通过对象调用)
适用场景 :当函数作为对象的方法被调用时,this
指向该对象。
javascript
const user = {
name: "Alice",
sayHello() {
console.log("Hello, " + this.name); // this指向user对象
}
};
user.sayHello(); // 输出:Hello, Alice
隐式丢失陷阱:当函数被单独提取出来调用时,会丢失绑定!
php
const fn = user.sayHello; // 提取函数
fn(); // 输出:Hello, undefined(this指向全局对象)
规则三:显式绑定(强制指定 this
)
通过 call
、apply
、bind
三种方法强制指定 this
的指向。
javascript
function introduce(lang) {
console.log(`我会说${lang},我叫${this.name}`);
}
const engineer = { name: "Bob" };
// 立即调用并指定this
introduce.call(engineer, "JavaScript"); // 输出:我会说JavaScript,我叫Bob
introduce.apply(engineer, ["Python"]); // 输出:我会说Python,我叫Bob
// 永久绑定(返回新函数)
const boundFn = introduce.bind(engineer);
boundFn("Java"); // 输出:我会说Java,我叫Bob
区别对比:
方法 | 参数传递方式 | 返回值 |
---|---|---|
call |
逗号分隔参数(如 call(obj, arg1, arg2) ) |
函数执行结果 |
apply |
数组传递参数(如 apply(obj, [arg1, arg2]) ) |
函数执行结果 |
bind |
同 call ,但返回新函数 |
新函数(永久绑定 this ) |
规则四:new
绑定(构造函数调用)
当使用 new
关键字调用函数时,会创建一个新对象,且 this
指向该对象。
javascript
function Car(brand) {
this.brand = brand; // this指向新创建的对象
}
const bmw = new Car("BMW");
console.log(bmw.brand); // 输出:BMW
new
的四个步骤:
- 创建一个空对象
const newObj = {};
- 绑定原型链
newObj.__proto__ = Car.prototype;
- 绑定
this
const result = Car.call(newObj, brand);
- 返回对象
return result || newObj;
额外补充:构造函数返回值对 this
的影响
通常情况下,构造函数默认返回新创建的对象(即 this
指向的对象)。但如果构造函数显式返回一个对象,则 new
调用返回的将是这个显式返回的对象,而不是 this
指向的对象。如果显式返回的不是对象(如基本类型),则忽略返回值,仍然返回 this
指向的对象。
javascript
function Person(name) {
this.name = name;
// 显式返回一个对象
return { message: 'This is a different object' };
}
const person = new Person('John');
console.log(person); // 输出:{ message: 'This is a different object' }
function Animal(type) {
this.type = type;
// 显式返回一个基本类型
return 123;
}
const animal = new Animal('Dog');
console.log(animal.type); // 输出:Dog
五、优先级排序:当规则发生冲突时
四条规则的优先级从高到低为:new
绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
实战验证
ini
function whoAmI() {
console.log(this.name);
}
const obj = { name: "Obj", whoAmI };
const bound = whoAmI.bind(obj); // 显式绑定
// 场景1:new绑定 vs 显式绑定
const instance = new bound(); // 输出:undefined(new绑定优先级更高,创建新对象,未初始化name)
// 场景2:隐式绑定 vs 默认绑定
const fn = obj.whoAmI;
fn(); // 输出:undefined(隐式丢失,触发默认绑定)
六、特殊场景与例外情况
6.1 被忽略的 this
:传入 null
或 undefined
当显式绑定时传入 null
或 undefined
,会被忽略,应用默认绑定:
javascript
function logThis() {
console.log(this);
}
logThis.call(null); // 浏览器中输出window(默认绑定)
logThis.call(undefined); // 同上
安全提示 :避免传入 null
作为 this
,可能意外污染全局对象。推荐用一个空对象替代:
javascript
const safeObj = Object.create(null); // 空对象,无原型链污染风险
logThis.call(safeObj); // 输出:{}
6.2 间接引用:赋值操作导致的绑定丢失
javascript
const obj = {
msg: "Hello",
say() {
console.log(this.msg);
}
};
// 间接引用:将方法赋值给变量
const fn = obj.say;
fn(); // 输出:undefined(this指向全局)
原理:赋值操作会导致函数脱离原对象,调用时触发默认绑定。
6.3 回调函数中的 this
在事件监听器、定时器等回调函数中,this
的指向往往会让人困惑。
事件监听器中的 this
在 DOM 事件监听器中,this
通常指向触发事件的 DOM 元素。
ini
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', function() {
console.log(this.textContent); // 输出:Click me
});
document.body.appendChild(button);
定时器中的 this
在 setTimeout
和 setInterval
的回调函数中,this
默认指向全局对象(在浏览器中是 window
)。
javascript
function Timer() {
this.time = 0;
setInterval(function() {
this.time++; // 这里的this指向window
console.log(this.time); // 输出NaN
}, 1000);
}
const timer = new Timer();
为了解决这个问题,可以使用箭头函数或者提前保存 this
的引用。
ini
function Timer() {
this.time = 0;
const self = this;
setInterval(function() {
self.time++;
console.log(self.time);
}, 1000);
}
const timer = new Timer();
七、箭头函数:颠覆传统的词法 this
7.1 箭头函数的特殊机制
箭头函数不使用传统的 this
绑定规则,而是继承外层作用域的 this
(词法作用域绑定)。
javascript
const obj = {
name: "传统函数",
traditional() {
console.log(this.name); // 输出:传统函数(隐式绑定)
setTimeout(function() {
console.log(this.name); // 输出:undefined(默认绑定,因为setTimeout的回调是独立函数)
}, 100);
},
arrow: () => {
console.log(this.name); // 输出:undefined(箭头函数继承外层的this,这里外层是全局作用域)
}
};
obj.traditional(); // 传统函数 undefined
obj.arrow(); // undefined(全局作用域中没有name变量)
7.2 经典应用:解决回调函数 this
丢失
在类组件中,传统函数需要手动绑定 this
,而箭头函数自动继承外层 this
:
arduino
class Button {
constructor(text) {
this.text = text;
// 传统写法:需要手动绑定
// this.handleClick = this.handleClick.bind(this);
}
// 箭头函数写法:自动绑定this
handleClick = () => {
console.log(this.text); // 正确输出按钮文本
}
}
const btn = new Button("点击我");
document.addEventListener("click", btn.handleClick); // 点击时输出:点击我
7.3 箭头函数不能使用 arguments
对象
箭头函数没有自己的 arguments
对象,它会继承外层作用域的 arguments
对象。
scss
function outer() {
const arrow = () => {
console.log(arguments[0]); // 继承outer函数的arguments
};
arrow();
}
outer(123); // 输出:123
八、this
判断流程图:一看就懂的决策指南

九、实战测试:从代码中理解 this
测试题
javascript
var name = "全局"; // 注意:var声明的变量在全局作用域中会成为window的属性
const obj = {
name: "对象",
foo: function() {
console.log(this.name); // 场景1
const inner = () => { // 箭头函数,继承外层this
console.log(this.name); // 场景2
};
inner();
},
bar: () => { // 箭头函数,外层是全局作用域
console.log(this.name); // 场景3
}
};
function test(fn) {
fn(); // 场景4
}
// 调用
obj.foo(); // 输出?
obj.bar(); // 输出?
test(obj.foo); // 输出?
答案解析
- 场景 1 :
obj.foo()
是隐式绑定,this
指向obj
,输出对象
。 - 场景 2 :箭头函数
inner
继承外层foo
的this
(即obj
),输出对象
。 - 场景 3 :
bar
是箭头函数,外层作用域是全局,this
指向window
,输出全局
。 - 场景 4 :
test(obj.foo)
中,obj.foo
被提取为独立函数,触发默认绑定,输出全局
。
十、this需要警惕之处
1. 避免 this
丢失的三大法宝
-
使用箭头函数:适用于回调函数、类方法。
-
提前绑定 :用
bind
永久绑定this
。
kotlin
class Timer {
constructor() {
this.time = 0;
setInterval(this.tick.bind(this), 1000); // 提前绑定this
}
tick() {
this.time++;
}
}
- 使用
that
或self
中转(传统写法):
javascript
function outer() {
const that = this; // 缓存this
function inner() {
console.log(that.value); // 使用that
}
}
2. 警惕全局作用域的 this
- 避免用
var
声明变量,改用let/const
,减少全局污染。 - 严格模式下,全局作用域的函数调用
this
为undefined
,避免意外赋值。
3. DOM 事件中的 this
javascript
// 错误:this指向DOM元素
element.addEventListener("click", function() {
this.style.color = "red"; // 正确,但需注意this是DOM元素
});
// 正确:显式绑定
element.addEventListener("click", function() {
this.style.color = "red"; // 这里的this是DOM元素,若需访问外部对象,需提前绑定
}.bind(obj));
十一、总结:this
的核心本质
-
this
不是静态的 :它的指向在函数调用时确定,取决于调用方式。 -
四大规则是核心 :默认、隐式、显式、
new
绑定,优先级依次升高。 -
箭头函数是特例 :它不绑定
this
,而是继承外层作用域的this
,适合处理回调。
理解 this
的关键在于:永远不要在函数定义时猜测 this
的指向,而是看函数如何被调用。多写代码、多调试,逐步建立 "调用时绑定" 的思维模型,你将彻底掌控这个 "魔法关键字"!