深度解析 JS 中的 this 指向:从底层逻辑到实战规则

25 阅读4分钟

前言

在 JavaScript 的面试和日常开发中,this 绝对是一个绕不开的“大山”。很多初学者会被它忽左忽右的指向搞得晕头转向。今天我结合自己的学习笔记,把 this 的来龙去脉和绑定规则彻底理清楚。希望对同样在进阶路上的你有所帮助!

一、 为什么我们需要 this?

很多同学会问:既然我可以直接引用对象名,为什么还要用 this? 核心价值:隐式传递对象引用。 this 提供了一种更优雅的方式来传递引用,使得代码更简洁、易于复用。

function identify() {
    return this.name.toUpperCase();
}

var me = { name: "Kyle" };
var you = { name: "Reader" };

identify.call(me);  // KYLE
identify.call(you); // READER

如果不使用 this,你就需要显式地将对象作为参数传递,代码会变得冗余且难以维护。

二、 this 到底出现在哪?

在 JavaScript 中,this 主要出现在两个地方:

  1. 全局环境:在浏览器环境下,this 直接指向 window 对象。
  2. 函数体内:这是最复杂的地方,this 的指向不是在函数创建时决定的,而是在函数被调用时决定的

三、 五大绑定规则

掌握了下面这五条规则,你就掌握了 this 的“密码”:

1. 默认绑定

当函数被独立调用(不带任何修饰的函数调用)时,函数中的 this 指向全局对象 window。

function foo() {
    console.log(this); 
}
foo(); // window

2. 隐式绑定

当函数被一个上下文对象所拥有,并被该对象调用时,this 指向该对象。

var obj = {
    a: 2,
    foo: function() { console.log(this.a); }
};
obj.foo(); // 2

3. 隐式丢失(就近原则)

这是一个细节:当函数被多层对象嵌套调用时,this 指向离它最近的那个对象。

var obj2 = {
    a: 42,
    foo: function() { console.log(this.a); }
};
var obj1 = {
    a: 2,
    obj2: obj2
};
obj1.obj2.foo(); // 42 (指向 obj2)

4. 显式绑定 (Explicit Binding)

显式绑定就像是给函数下达“死命令”,强制它在执行时将 this 指向我们指定的对象。

① call —— 逐个传参的“指挥官”

call 会立即执行函数。它的第一个参数是 this 的指向,后面的参数需要一个一个列出来。

function greet(skill, hobby) {
    console.log(`我是${this.name},我会${skill},喜欢${hobby}`);
}

const user = { name: "阿强" };

// 语法:fn.call(thisArg, arg1, arg2, ...)
greet.call(user, "JavaScript", "代码"); 
// 输出:我是阿强,我会JavaScript,喜欢代码

② apply —— 数组传参的“打包员”

apply 的功能和 call 完全一样,唯一的区别是:它接收参数的方式是数组。这在处理动态参数(如获取数组最大值)时非常有用。

const user = { name: "阿珍" };

// 语法:fn.apply(thisArg, [argsArray])
greet.apply(user, ["Python", "看书"]);
// 输出:我是阿珍,我会Python,喜欢看书

③ bind —— 延后执行的“契约书”

bind 不会立即执行函数,而是返回一个绑定了新 this 的新函数。你可以随时在需要的时候调用它。

const user = { name: "老王" };

// 语法:const newFn = fn.bind(thisArg, arg1, ...)
const bindGreet = greet.bind(user, "Vue", "钓鱼");

// 此时不会有输出,直到你手动调用它
bindGreet(); 
// 输出:我是老王,我会Vue,喜欢钓鱼

💡 快速对比表

为了方便记忆,我总结了一个对比表,大家可以直接保存:

方法立即执行传参方式常用场景
call参数列表 (arg1, arg2)对象的属性继承、借用构造函数
apply数组形式 ([args])与 arguments 配合、操作数组
bind参数列表 (arg1, arg2)React/Vue 中的回调函数绑定、延迟执行

面试小贴士: 如果 call/apply/bind 的第一个参数传入了 null 或 undefined,那么在非严格模式下,this 会自动指向全局对象 window。

5. new 绑定

使用 new 关键字调用构造函数时,JS 内部会创建一个新对象,并把构造函数里的 this 绑定到这个新对象上。

function Person(name) {
    this.name = name;
}
var me = new Person("Jay");
console.log(me.name); // Jay

四、 特殊存在的箭头函数

箭头函数没有自己的 this! 这是它和普通函数最大的区别。箭头函数的 this 是在定义时捕获自外层(父级)非箭头函数的作用域。

注意: 箭头函数的 this 一旦确定,就无法通过 call/apply/bind 再次修改。

总结

  • 独立调用看 window。
  • 对象调用看对象。
  • 多层对象看最近。
  • call/apply/bind 看第一个参数。
  • new 看实例。
  • 箭头函数看它亲爹(外层作用域)。