回归基础:理解并掌握 JavaScript 中的“this”

2025-05-28

回归基础:理解并掌握 JavaScript 中的“this”

最近我一直在思考this很多事情,因为我的 Web 代码中有很多链式回调函数。这是一个很好的机会,可以让我回归基础,回顾一下thisJavaScript 的工作原理,以及有哪些工具可以解决它的怪癖。

对于从 Java 或 Swift 等更典型的面向对象语言转行过来的新开发者来说,JavaScript 对this关键字的奇怪使用是一个陷阱,随时可能让你的代码崩溃。如果你使用 React 的类组件,这种情况尤其危险,因为你经常在类中定义方法作为回调处理程序。如果你盲目地认为它this会按照你期望的方式运行,那你就会遇到麻烦。所以,让我们了解一下this这个敌人,以便学习如何对抗它:

什么是this

这是什么

this让我们先从在最佳情况下我们期望如何工作的基本知识开始:

'use strict';

class Person {
  name;

  constructor(theirName) {
    this.name = theirName;
  }

  introduce() {
    console.log("Hello I'm " + this.name);
  }
}

const william = new Person("Bill");
william.introduce(); // Prints out "Hello I'm Bill"
Enter fullscreen mode Exit fullscreen mode

这很简单:有一个名为 的对象类Person。每个对象Person都记住一个名为 的变量name,并有一个名为 的方法introduce。当你调用introduce某个人时,它会查看该人的name简介并打印出来。所以,是对我们正在查看的this实例的对象的引用,对吗?introduce

嗯,不完全是。看看这个:

// Continued from above

// This doesn't RUN william's introduce function,
// it makes a REFERENCE to it
const introduceWilliam = william.introduce;

// Because it's a reference to a method that worked,
// we might assume the reference will also work but...
introduceWilliam();
// Uncaught TypeError! Cannot read property 'name' of undefined
Enter fullscreen mode Exit fullscreen mode

现在,我们深入研究了平静的表面之下90年代编写的函数式编程语言的黑暗深处。

你必须记住,就 JavaScript 而言,函数只是另一种对象。它们可以在任何地方存储、传递和执行。

当你调用 时someThing.someFunc(),JavaScript 会解析出你想在的上下文中执行 中的指令someFuncsomeThing。也就是说,设置thissomeThing,然后执行指令。

但是如果你引用了someFunc,你就可以在任何地方执行它。上面我们在全局上下文中调用了它,这在严格模式下会保留thisas undefined。你甚至可以使用函数callapply方法(函数上的函数!)来提供你想要的任何上下文和参数。

让我们编写一些有点可怕的代码来证明这一点:

// Still using william from above
const william = new Person("Bill");
// Make a reference to william's introduce method
let introduce = william.introduce;

// Make an unrelated object - Bagel the Beagle
const puppy = { name: "Bagel", breed: "Beagle" };
// Run function with manual `this` - Dogs can talk now
introduce.call(puppy); // Prints "Hello I'm Bagel"
Enter fullscreen mode Exit fullscreen mode

驯服this野兽

这非常强大,而且往往是不必要的。就像许多非常强大的东西一样,它也非常危险。由于我们经常传递函数引用(例如,this用作buttons 或s 的回调), 的未绑定特性随时可能让你陷入困境。formthis

那么,我们该如何驯服this?我可以挥舞着拐杖,嘶哑地对你说:“嗯,回到**我的*日子……*” 但事实是,JavaScript 的 ES5 和 ES2015 修订版为我们提供了限制游移this值所需的一切:

函数.prototype.bind()

ES5 中添加的第一个工具是函数,这是2000 年代各种实用程序库所创新的黑客bind()标准化。this

// Bind this reference to introduce so this is ALWAYS william.
let alwaysIntroduceWilliam = william.introduce.bind(william);

alwaysIntroduceWilliam(); // Prints "Hello I'm Bill"
alwaysIntroduceWilliam.call(puppy); // Prints "Hello I'm Bill"
Enter fullscreen mode Exit fullscreen mode

bind顾名思义,它的作用就是将函数绑定到一个选定的上下文中this,确保其中的指令始终在我们选择的上下文中运行。在这里你可以看到,即使我们尝试使用call来设置不同的this, 也会bind超过 ,并且我们总是会引入william。这是修复 的重要第一步this,但如今它不太常用,因为……

带箭头 =>

ES2015 新增了箭头函数,它(几乎是无意中)为我们提供了最常用的固定this值获取方式。这是因为箭头函数在其定义上下文中创建了一个闭包。这意味着箭头函数内部引用的所有变量始终指向与箭头函数首次解析时相同的内存位置。

这对于捕获局部变量以便稍后使用非常有用,但它还有一个额外的好处,就是捕获this箭头定义时设置的值。而且,由于this(基本上)总是在构造过程中创建的对象,我们可以使用箭头函数来创建行为与this我们预期完全一致的方法:

// Rewriting Person with arrows
class ArrowPerson {
  name;

  constructor(theirName) {
    this.name = theirName;
  }

  introduce = () => {
    // The arrow captures `this` so it is actually a
    // reference to THIS Person.
    console.log("Hello I'm " + this.name);
  }
}

const arrowBill = new ArrowPerson("Arrow Bill");
arrowBill.introduce(); // "Hello I'm Arrow Bill"

// Now `this` is fixed even as we pass the function around:
const introduceRef = arrowBill.introduce;
introduceRef(); // "Hello I'm Arrow Bill"
introduceRef.call(puppy); // "Hello I'm Arrow Bill"
Enter fullscreen mode Exit fullscreen mode

this现在一切都更有意义了

我希望你this现在能理解得更清楚一些。说实话,光是这些都写出来,我就觉得理解得更透彻了。而且,由于 JavaScriptthis会影响所有转译成 JavaScript 的代码,希望这也能帮助你理解 Typescript 等其他语言中函数上下文的复杂之处。

如果您对 有任何疑问this,请在下方评论区留言。即使从事网络写作多年,我仍然在学习,所以我确信其中肯定存在一些可怕的危险,以及一些this我忘记或还不知道的有趣事实。

文章来源:https://dev.to/zackdotcomputer/back-to-basics-understanding-and-conquering-this-in-javascript-4pbd
PREV
使用 Node.js 构建 Restful CRUD API
NEXT
软件架构简介(单片架构、分层架构、微服务架构)