🔍 1 前言
在 JavaScript 语言中,“一切皆对象”的特性深入人心,甚至其运行环境本身也是对象。这意味着 所有函数都依托于某个对象运行,而 this
所代表的,正是函数运行时所处的对象(即其运行环境)。
从本质上理解,this
的概念相当直观。然而,JavaScript 的动态语言特性带来了核心挑战:函数的运行环境会随着代码的执行过程而动态改变。这种动态性直接导致了 this
的指向也随之实时变化,使得我们无法在代码编写阶段就确定它最终指向哪个对象。正因如此,this
成为了 JavaScript 开发中一个广为人知的难点。
本文将深入剖析 this
的由来、核心指向规则,以及绑定 this
的具体方法。
🎯 2 this 是什么
2.1 概念
如前所述,this
会随函数的运行环境动态变化。但无论环境如何变换,this
始终指向一个对象。简而言之,this
代表当前属性或方法所属的环境对象,其指向会随着当前环境对象的变更而变更。
2.2 本质
如需回顾引用数据类型的存储方式,请参考这篇文章:【JavaScript】🔍 变量变形记:JavaScript 数据类型的 8 种分身术!
var A = { a: 123 };
this
的出现与内存的数据结构密切相关。在讨论引用数据类型时提到,当我们将一个对象赋值给变量 A
时,JS 引擎会先在堆内存中创建该对象,然后将对象的引用地址赋值给变量 A
。访问 A.a
时,需要先通过变量 A
获取地址,再根据地址找到原始对象并返回其 a
属性。
对象的属性也可以是函数:
var A = {
a: function() {}
};
此时,A.a
存储的是另一个地址(指向函数)。JavaScript 允许函数在其内部访问当前环境的变量,而函数的运行环境又会动态变化。因此,需要一种机制告知函数其当前的运行环境——这就是 this
。
const a = 1;
function fn() {
console.log(this.a);
}
const obj = {
a: 2,
fn: fn
};
fn(); // -> 1 (非严格模式,this 指向全局对象)
obj.fn(); // -> 2 (this 指向 obj)
⚖️ 3 this 的指向规则
-
独立函数调用:
- 严格模式下 (
'use strict'
):this
绑定为undefined
。 - 非严格模式下:
this
绑定为全局对象 (globalThis
,浏览器中为window
,Node.js 中为global
)。
const o1 = { text: 'o1', fn: function() { return this.text; } }; const o3 = { text: 'o3', fn: function() { var fn = o1.fn; // 将 o1.fn 方法的引用赋值给变量 fn return fn(); // 独立调用 fn(), this 丢失 o1 的绑定 } }; console.log(o1.fn()); // -> 'o1' (通过 o1 调用,this 指向 o1) console.log(o3.fn()); // -> undefined (独立调用 fn(), 非严格模式 this 指向全局,全局无 text 属性)
- 严格模式下 (
-
构造函数调用 (使用
new
关键字):this
指向新创建的实例对象(这与new
运算符的内部机制有关)。如需回顾
new
的原理,请参考:【JavaScript】✨ JavaScript 对象 & 包装对象:魔法世界大冒险! -
显式绑定 (使用
call
,apply
,bind
方法): 函数体内的this
会被明确指定为传入的第一个参数所指向的对象(详见下文 4.2 节)。 -
隐式绑定 (通过上下文对象调用): 当函数作为一个对象的属性被调用时(
obj.fn()
),函数体内的this
会被绑定到该对象 (obj
) 上。const o1 = { text: 'o1', fn: function() { return this.text; } }; const o2 = { text: 'o2', fn: function() { return o1.fn(); // 虽然是在 o2 的方法里调用,但实际执行的是 o1.fn() } }; console.log(o1.fn()); // -> 'o1' (this 指向 o1) console.log(o2.fn()); // -> 'o1' (o1.fn() 被独立调用,规则1,非严格模式 this 指向全局或 undefined) // 注意:o2.fn() 内部的 o1.fn() 是独立调用,而非通过 o2 的上下文调用。
-
箭头函数: 箭头函数本身没有自己的
this
。它的this
继承自定义它时所处的父级作用域(词法作用域),且在函数生命周期内固定不变。箭头函数不能用作构造函数。// 箭头函数示例 var a = 1; var obj = { a: 2, bar: function() { // bar 是普通函数,this 指向调用它的对象 obj const baz = () => { // 箭头函数 baz 在 bar 内部定义,继承 bar 的 this (指向 obj) console.log(this.a); }; baz(); // 调用 baz }, }; obj.bar(); // -> 2 (箭头函数 baz 的 this 继承自 bar 的 this, 即 obj)
🛠️ 4 绑定 this 的方法
🔗 4.1 隐式绑定
当函数作为某个对象的属性被调用时(即拥有上下文对象),隐式绑定规则会自动将函数内部的 this
绑定到该上下文对象上。
// 隐式绑定示例
var a = 1; // 全局变量 a
function foo() {
console.log(this.a);
}
var obj = {
a: 2, // 对象属性 a
foo: foo // 引用全局函数 foo
};
obj.foo(); // -> 2
// 解释:foo 作为 obj 的属性被调用 (obj.foo()),因此 foo 内部的 this 指向 obj,访问 obj.a 得到 2
可以将 obj.foo()
理解为:在 obj
的地址空间中找到 foo
函数并执行它,因此 foo
执行时的环境对象是 obj
,this
自然指向 obj
。
🔧 4.2 显式绑定
JavaScript 提供了三个内置方法 (call
, apply
, bind
),允许我们显式地设置函数调用时的 this
值。
🪄 4.2.1 Function.prototype.call(thisArg, ...args)
- 立即调用目标函数。
- 第一个参数
thisArg
:指定函数内this
应指向的对象。 - 后续参数:以逗号分隔的形式直接传递给目标函数。
🧩 4.2.2 Function.prototype.apply(thisArg, [argsArray])
- 立即调用目标函数。
- 第一个参数
thisArg
:指定函数内this
应指向的对象。 - 第二个参数
argsArray
:一个数组(或类数组对象),包含传递给目标函数的参数。
🎭 4.2.3 Function.prototype.bind(thisArg, ...args)
- 不会立即调用目标函数,而是返回一个新的函数。
- 第一个参数
thisArg
:指定新函数内部永久绑定的this
值。 - 后续参数(可选):在绑定时可以预先传入部分参数(柯里化),剩余参数可以在新函数被调用时再传入。
- 常用于需要延迟执行或需要固定
this
值的场景(例如事件处理函数)。
const person = {
name: "张三",
age: 30,
};
function introduce(city, hobby) {
console.log(
`大家好,我是${this.name},今年${this.age}岁,来自${city},喜欢${hobby}。`
);
}
// 1. 使用 call() 方法
console.log("=== call() 演示 ===");
introduce.call(person, "北京", "游泳"); // -> "大家好,我是张三,今年30岁,来自北京,喜欢游泳。"
// 2. 使用 apply() 方法
console.log("\n=== apply() 演示 ===");
introduce.apply(person, ["上海", "读书"]); // -> "大家好,我是张三,今年30岁,来自上海,喜欢读书。"
// 3. 使用 bind() 方法
console.log("\n=== bind() 演示 ===");
const partiallyBound = introduce.bind(person, "深圳"); // 绑定 this 为 person, 预先传入 city='深圳'
partiallyBound("旅行"); // 调用新函数,传入 hobby='旅行' -> "大家好,我是张三,今年30岁,来自深圳,喜欢旅行。"
// 额外演示:绑定到不同对象
const anotherPerson = {
name: "李四",
age: 25,
};
console.log("\n=== 绑定到不同对象 ===");
introduce.call(anotherPerson, "杭州", "爬山"); // -> "大家好,我是李四,今年25岁,来自杭州,喜欢爬山。"