一劳永逸地搞懂 JavaScript中‘this’

2024-02-12 08:26:19 浏览数 (1)

免费体验 Gpt4 plus 与 AI作图神器,我们出的钱 体验地址:体验

引言:this 在 JS 中 —— 为什么它如此重要

你是否曾觉得 JavaScript 似乎在戏弄你,尤其是当它在你面前挥舞 this 关键字时?那种“好吧,JavaScript,今天我们玩什么游戏?”的感觉。特别是当你试图确定在某段代码中 this 引用的是什么时。

理解 this 就像在JavaScript广阔的领域中握住指南针。这不仅仅是为了弄清楚一个关键字;它是为了打开通往高级编码技巧和模式的大门。

为什么我们应该关心“this”?

  • 普遍性:就像你无法逃避的流行曲调, this 在JavaScript中随处可见。从小脚本到庞大的Web应用程序,它都会显现出来。
  • 提高水平:解读 this 意味着你正在走向像经验丰富的专家那样的编码。这是更接近健壮且无错误的脚本的一步。
  • 变色龙行为this 在许多情境中的含义都会变化,这使它既有趣又时而令人困惑。

接下来会发生什么?

做好准备,因为我们即将一劳永逸地揭开this的神秘面纱。我们将探索其所有的细微差别,涵盖你可能遇到的每一个场景。不再有猜测或挠头的困惑!

准备深入了解吗?无论你是编写脚本多年还是刚开始JavaScript之旅,让我们携手共进,一起解开this的神秘面纱!

“this”在全局上下文中:从基础开始

当你刚开始理解JavaScript中的 this 关键字时,最好从头开始 —— 也就是全局上下文。但是,我们说的全局上下文是什么意思呢?

简单地说,全局上下文是默认的、顶级的环境,当你的代码不在任何函数或对象内部时,它就位于这个环境中。那么,在这里 this 是如何表现的呢?

在浏览器中:

如果你在浏览器中运行你的 JavaScript 代码(像我们大多数人经常做的那样),全局上下文中的this指的是window对象。这是因为,在浏览器中,window 对象就是全局对象。

代码语言:javascript复制
console.log(this === window); // true
var variable = “我是一个全局变量!”;
console.log(this.variable); // “我是一个全局变量!”

在这里,当我们声明变量时,它被附加到 window 对象上。因此,在全局上下文中使用this.variable 会给我们那个变量的值。

在Node.js中:

如果你在Node.js环境中运行你的代码,情况会有所不同。在Node.js中,this 的顶级值是一个空对象,不等同于 global

代码语言:javascript复制
console.log(this); // {}
global.globalVar = “我在Node中的全局对象上!”;
console.log(global.globalVar); // “我在Node中的全局对象上!”

了解 this 在全局上下文中的行为可能会根据代码的执行位置而有所不同。

那么,为什么这很重要?

掌握全局上下文中的 this 为理解其在更复杂场景中的行为提供了基础。当你深入JavaScript时,你会发现有些情况下,函数或方法是从全局上下文中调用的,理解这种行为变得至关重要。

通过掌握基础知识,当我们深入探讨 this 的后续部分时,你为自己奠定了成功的基础。

“this”在常规函数中:上下文是关键

啊,常规函数。与它们的新型箭头函数表亲相比,它们可能看起来有点老派,但它们仍然是JavaScript的基础部分。当涉及到这些函数内部“this”的行为时,事情可能会变得有点棘手。

基本行为:

在其核心,常规函数内部的 this 值是由如何调用该函数(其调用上下文)来确定的。让我们分解一下:

直接调用函数:

当你在全局上下文中调用一个函数时,this 将引用全局对象。

代码语言:javascript复制
function showThis() {
console.log(this);
}
showThis(); // 在浏览器中是‘window’,在Node.js中是‘global’

作为对象内的方法:

当一个函数被定义为对象方法时,this 将引用拥有该方法的对象。

代码语言:javascript复制
const obj = {
name: “John”,
sayName: function() {
console.log(this.name);
}
};
obj.sayName(); // “John”

在这里,sayName 方法使用 obj 对象调用。因此,sayName 内部的 this 指的是 obj

特殊情况

使用call、apply和bind:

这些是允许你直接设置 this 应该引用什么的方法,而不考虑函数如何或在哪里被调用。

call和apply立即用指定的上下文调用函数。

代码语言:javascript复制
function greet(greeting) {
console.log(greeting   ‘, ‘   this.name);
}
const person = { name: “Alice” };
greet.call(person, “Hello”); // “Hello, Alice”

bind返回一个带有绑定上下文的新函数,但不立即执行它。

代码语言:javascript复制
const boundGreet = greet.bind(person);
boundGreet(“Hi”); // “Hi, Alice”
为什么上下文很重要?

理解常规函数内部this的行为都是关于上下文的。它是关于知道在调用时哪个对象“拥有”函数。这种洞察可以防止无数的错误和挫败感,尤其是当你的JavaScript项目在复杂性上增长时。

请记住,在JavaScript的错综复杂的迷宫中,this 关键字是你的光明之光。在常规函数的世界中,它是上下文,确保你总是在正确的轨道上。当我们进一步探索时,观察 this 和上下文之间的动态舞蹈,适应语言的不同节奏。

箭头函数和“this”:游戏规则改变者

好吧,让我们谈谈箭头函数。当ES6出现时,它带来了这种写函数的新方法,不仅看起来更简洁,而且还改变了我们对 this 的看法。

这都是关于你来自哪里的

你知道在生活中,我们中的一些人根据我们所在的地方和我们与谁在一起会戴不同的帽子吗?常规函数也做类似的事情与 this。他们可以是变色龙,根据他们如何被调用来改变 this 引用什么。

而箭头函数呢?他们是直接的。他们从他们的周围抓住 this 的值,并坚持使用它。无论他们去哪里或如何被使用。

代码语言:javascript复制
const person = {
  name: ‘Anna’,
  activities: [‘reading’, ‘hiking’],
  printActivities() {
    this.activities.forEach(activity => {
    console.log(${this.name} loves ${activity});
  });
 }
};
person.printActivities();
// 输出:
// Anna loves reading
// Anna loves hiking

注意 forEach 中的箭头函数吗?它舒适地使用 printActivities 中的 this。没有戏剧。

但有一个问题

箭头函数有点固执。我们用来为常规函数设置 this 的方法,如 callapplybind?它们不适用于箭头函数。

代码语言:javascript复制
const obj = {
value: ‘hello’,
  print: () => {
    console.log(this.value);
  }
};
const anotherObj = { value: ‘world’ };
obj.print.call(anotherObj); // 输出:undefined

我们试图用 call 偷偷摸摸,但 print 中的箭头函数就像,“我知道我是谁!”并坚持使用它的原始 this

所以,箭头还是不箭头?

箭头函数就像你拥有的那个最喜欢的工具 —— 超级有用,但不适合每一项工作。当你想保持 this不变时,尤其是在回调中,它们是非常有价值的。但是当你需要一些灵活性时?经典函数可能是你的朋友。

在IIFEs中:this 的独特角色

在我们深入了解IIFE中的 this 之前,让我们澄清一下什么是IIFE。想象一下:你刚写了一个函数,而在你有机会坐下来欣赏你的作品之前,它已经开始工作了。那就是IIFE!

IIFE(立即调用的函数表达式)就像那个朋友,一做完计划就立刻行动。一旦定义,砰 —— 它就运行了。而且,仅仅因为它速度快并不意味着它不是多才多艺的。你可以用标准函数、箭头函数,甚至加入一些 async-await 魔法来制作它。它们是这样的:

代码语言:javascript复制
// 经典的方式
(function () {
  console.log(“这是一个IIFE!”);
})();

// 现代的、箭头函数的变种
(() => {
  console.log(“箭头函数IIFE正在行动!”);
})();

// 对于那些异步的冒险
(async () => {
  await console.log(“Async-await与IIFE结合?是的!”);
})();

现在,进入IIFEs中的 this及其独特的舞蹈。当你在IIFE内部,你看着 this,你基本上在看全局对象。在浏览器世界中,那是我们的可靠朋友,window

代码语言:javascript复制
(function () {
    console.log(this);  // Outputs: Window {...} in browsers
})();

但这里有一个转折:用神秘的 use strict 声明开始你的IIFE,这就会有一个身份危机。而不是指向全局对象,它只会坐在那里,直到你用像 callapply 这样的方法给它一些目的。

代码语言:javascript复制
(function () {
    'use strict';
    console.log(this);  // Outputs: undefined
})();

this 在事件处理程序中:与DOM交互

让我们描绘一个画面。你在一个网页上,你最喜欢的歌正在播放,有一个按钮在那里诱惑你点击它。在你知道之前,JavaScript的魔法就活了起来,事情开始发生。但你有没有想过内部工作,使这些DOM元素跳舞的隐藏的木偶线?在这个魔法的核心是我们的好朋友:this

主要吸引力:事件监听器

当你将一个事件监听器绑定到一个DOM元素时,你基本上是在耳语指示,告诉它,“嘿,当有人与你互动时,做这件事。”当那个“事情”涉及到使用 this 时,它通常指的是事件被调用的元素。

代码语言:javascript复制
const button = document.querySelector(‘#myButton’);
  button.addEventListener(‘click’, function() {
  console.log(this.innerHTML); // 输出:#myButton内的任何文本
});

在这种情况下,this 直接指向按钮。就像按钮说,“是的,我是被点击的那个!”

情节转折:箭头函数

现在,如果你想在事件监听器中使用箭头函数,要小心。记住我们之前讨论过箭头函数从它们的周围继承 this 吗?好吧,这意味着它们不像常规函数那样绑定自己的 this

代码语言:javascript复制
button.addEventListener(‘click’, () => {
  console.log(this.innerHTML); // 哎呀!这不会按预期工作。
});

在这个设置中,this 不指向我们的按钮。它可能指向窗口或另一个外部范围,导致出现意外的结果。

动态事件:手动设置 this

有时,你需要更多的控制,你可能想要指定 this 引用什么。对于这样的情况,我们有 bind 来拯救!

代码语言:javascript复制
function displayText() {
  console.log(this.innerHTML);
}
const boundFunction = displayText.bind(button);
button.addEventListener(‘click’, boundFunction);

在这里,无论 displayText 从哪里调用,使用 bind我们都坚定地告诉它考虑按钮作为它的this

“this”在构造函数中:带有上下文的建筑

想象一下你是一名建筑师。你手里有蓝图,材料准备好了,每次你开始一个新项目,你都会建造结构,虽然基于类似的设计,但都有自己独特的性格和身份。在JavaScript中,当我们谈论构造函数时,我们实际上是在讨论这些主要的蓝图,它们产生了独特的对象。正如你可能猜到的,this 在个性化这些创作中起到了关键的作用。

基础:构造函数

在其核心,构造函数只是一个函数。但它是一个有抱负的函数。它梦想着创建多个对象,每个对象都是根据其框架塑造的,但持有自己的一套值。

代码语言:javascript复制
function Car(make, model) {
  this.make = make;
  this.model = model;
}
const myCar = new Car(‘Toyota’, ‘Corolla’);
console.log(myCar.make); // 输出:Toyota

注意我们如何在构造函数中使用 this?这是我们说的,“对于每一辆新车,将给定的制造和模型分配给这个特定的实例。”

旋转:原型方法

使用构造函数的一个好处是能够将方法附加到它们的原型上。这些方法可以通过 this 访问实例特定的数据,使它们相当动态。

代码语言:javascript复制
Car.prototype.displayInfo = function() {
  console.log(这辆车是一个${this.make} ${this.model}。);
};

myCar.displayInfo(); // 输出:这辆车是一个Toyota Corolla。

在这里,displayInfo 方法使用 this 来访问个别汽车的制造和模型,尽管该方法在实例之间是共享的。

小心:箭头函数陷阱

快速提醒!记得我们之前关于箭头函数的聊天吗?即使在这里,它们与 this 的行为也是一致的。所以,如果你试图用一个箭头函数来制作一个构造函数...好吧,期望一些怪癖。

代码语言:javascript复制
const Gadget = (name) => {
this.name = name;
}
// const newGadget = new Gadget(‘Phone’); // 这会抛出一个错误!

在上面的例子中,箭头函数不创建自己的 this。相反,它从其封闭的范围继承它,当用作构造函数时,可能会导致意外的结果。

与“this”有关的常见失误:要注意什么

我们认为我们已经掌握了它的时候,它给了我们一个曲线球。这就像试图抓住一个滑溜的鱼;一旦你失去焦点,它就消失了。但不要担心,我们在这里帮助你避免一些常见的陷阱。

  1. 忘记“new”:当你使用构造函数创建一个新对象时,确保使用new关键字。否则,this将指向全局对象,可能会导致意外的结果。
代码语言:javascript复制
function Dog(name) {
this.name = name;
}
const myDog = Dog(‘Buddy’); // Oops! 我们忘记了‘new’
console.log(window.name); // 输出:Buddy
  1. 事件监听器和回调:当你在事件监听器或回调函数中使用this时,确保你知道它引用的是什么。如果需要,使用bind或箭头函数来确保正确的上下文。
  2. 构造函数和箭头函数:如前所述,箭头函数不绑定自己的 this。尝试使用它们作为构造函数可能会导致错误。
  3. 方法和对象字面量:当你在对象字面量中定义方法时,如果你使用箭头函数,this将不会指向该对象。确保使用常规函数语法。
代码语言:javascript复制
const person = {
  name: ‘Ella’,
  greet: () => {
    console.log(‘Hello, ‘   this.name); // Oops! 这不会按预期工作。
  }
};
person.greet(); // 输出:Hello, undefined
  1. 动态方法:当你动态地添加方法到一个对象时,确保你知道this引用的是什么。如果需要,使用bind来确保正确的上下文。
代码语言:javascript复制
const cat = {
  name: ‘Whiskers’
};
function purr() {
  console.log(this.name   ‘ is purring...’);
}
cat.purr = purr;
cat.purr(); // 输出:Whiskers is purring...

但如果我们这样做:

代码语言:javascript复制
const purring = cat.purr;
purring(); // Oops! 这不会按预期工作。

总结:“this”之旅的终点

我们已经走过了一段漫长的旅程,探索了JavaScript中 this 关键字的各个方面。从全局上下文到构造函数,从事件处理程序到常规函数,我们已经涵盖了你可能遇到的每一个场景。

现在,你已经武装了自己,准备好在你的代码中恰当地使用 this。无论你是在创建一个小型脚本还是一个庞大的Web应用程序,你都知道如何导航 this 的变化多端的行为。

但请记住,学习是一个持续的过程。随着你继续编写更多的JavaScript代码,你可能会遇到新的挑战和情境。但现在,你已经有了一个坚实的基础,可以帮助你在遇到困难时找到正确的方向。

0 人点赞