通过举办户外野餐活动解释 JavaScript 的 apply、call 和 bind

2025-06-07

通过举办户外野餐活动解释 JavaScript 的 apply、call 和 bind

如果您曾经在家庭活动或聚会上负责操作烤架,那么您就可以理解 JavaScript 中的应用、调用和绑定。

如果您想编写清晰的代码,以便您(或队友)以后可以重新阅读,这里有一个常见的规则:不要重复自己!

如果你创建重复的方法或函数,你的代码以后会更难维护。仅仅因为你忘记更新同一段代码的多个版本,就会导致 bug 的产生。

如果你对 JavaScript 中的 this 概念有深入的理解,你就会知道,当你尝试追踪执行上下文时, this 会特别具有挑战性。执行上下文指的是函数被调用对象之间的关系。

为了编写更简洁的代码,你可以使用apply、call 和 bind
方法
来有目的地操作执行上下文。不同的对象可以共享方法,而无需为每个对象重写它们。

Apply、call 和 bind 有时也称为函数方法,因为它们与函数一起调用。

如果您正在寻找更技术性的解释,我推荐JavaScriptIsSexy的指南

这到底和烹饪有什么相似之处?

这三种方法有点像运用烹饪技巧来准备户外野餐的食物。想想你在烹饪过程中可能需要用到的不同场景:

  1. 几乎可以随时烹制并让每个人都开心的普通餐点(意大利面和酱汁)
  2. 野餐也可能是一场聚会(汉堡、热狗等)
  3. 为您和您的伴侣准备的豪华晚餐(鱼和酒)
  4. 为聚餐活动制作甜点(蛋糕)

每道菜都需要一套不同的烹饪技巧。有些菜品针对具体情况而定,而有些则比较通用。我稍后会详细解释。

在这种情况下,每个烹饪情境都有点像一个对象。例如,如果你说你要在烤架上烹饪,那就意味着你掌握了一些技能……比如操作烤架!

因此,如果我们对您可能使用的每种烹饪技术都有一种单独的方法,那么每个对象都会有一些独特的方法,并且在某些情况下一种方法可以应用于多个对象。

function boilWater(){};
let generalMeal = "pasta"; 

let cookout = {
  grill: function() {}
} 

let fancyDinner = {
  useOven: function() {}
} 

let dessert = {
  bakeCake: function() {}
}
Enter fullscreen mode Exit fullscreen mode

图表烹饪环境

在上面的代码中,烧水是一项通用技能,可能可以在任何情况下应用。

让我们举个例子。 grill()方法
位于 cookout对象的上下文中。这意味着,如果你正在举办一次户外野餐,你预计需要调用烧烤技能。

但是等等。户外野餐结束后,你不会忘记如何使用烤架!假设你和你的伴侣想做一份牛排来做一顿精致的晚餐,就像 fancyDinner 对象一样。你仍然希望能够从 cookout 对象中借用 grill() 方法。这时,apply、call 和 bind 就派上用场了。

烹饪技巧(方法)和烹饪环境
(对象)之间的这种关系将是我展示如何使用apply、call和bind()的主要方式。

为了理解本教程,你需要理解JavaScript 中的this 。如果你需要复习一下,可以看看我关于 JavaScript this 的教程。

Bind 方法简介

假设你正在为儿子或女儿的10岁生日举办一场户外野餐派对。你想在烤架上烤三种肉来满足所有人:鸡肉、汉堡和牛排。显然,派对上的每个人都是肉食爱好者。

然而,你根本不知道每个人想要什么!所以你需要在每位客人到达派对时询问他们。每种肉类通常都需要相同的步骤:

  1. 加入调味料
  2. 把它放在烤架上
  3. 经过一段时间后从烤架上取下

所以,为每种肉类单独写一个烹饪方法毫无意义。唯一的区别在于烹饪时间。汉堡需要15分钟,鸡肉需要20分钟,牛排需要10分钟。

我们希望对所有这些类型的肉都采用相同的通用流程。具体细节会有所不同。

你可能会想:“哦,这可是个办活动的好时机!” 但实际情况比这要复杂得多。正如我们上面所说,我们试图用执行情境的概念来展示我们的烹饪技巧。你肯定不想第一次就为整个聚会做汉堡、鸡肉和牛排。所以,我们必须展现你多年来积累的烹饪技能,以及你将如何将这些技能运用到这个特定的场景中。

let cookout={
  grill: function(duration, meat, name){
    console.log("Thanks " + name + "! Your " + meat + "will be ready in " + duration + "minutes.");
  }
}
Enter fullscreen mode Exit fullscreen mode

grillMethod1.png

在这种情况下,我们的 grill 方法仅记录了一句关于每个人的食物何时准备好的语句。我们将使用 bind() 来存储执行上下文。需要明确的是,执行上下文包含两个重要的细节。

  1. 对cookout对象的引用,以确保我们使用正确的对象
  2. 烹饪分钟数
let cookBurger = cookout.grill.bind(cookout, 15, "burger"); 
let cookChicken = cookout.grill.bind(cookout, 20, "chicken"); 
let cookSteak = cookout.grill.bind(cookout, 10, "steak");
Enter fullscreen mode Exit fullscreen mode

这代表了我们关于如何烹饪不同种类肉类的现有知识。在每种情况下,我们都会存储烹饪对象和烹饪所需时间,以便快速处理所有派对参与者的请求。

每个变量——cookBurger、cookChicken 和 cookSteak——都是一个新函数,可以随时执行,只需添加一个参数:人员姓名。以下是三个人和他们的食物要求:

  1. 杰克想要一个汉堡
  2. 吉尔想要牛排
  3. 大卫想要鸡肉

通过使用我们的新函数,我们可以快速处理这些请求,而无需重写 grill 方法。以下每个示例都采用了函数在 cookout 对象上下文中执行所需的最后一个参数。

cookBurger("Jack")
// Thanks Jack! Your burger will be ready in 15 minutes. 

cookSteak("Jill")
// Thanks Jill! Your steak will be ready in 10 minutes. 

cookChicken("David")
// Thanks David! Your chicken will be ready in 20 minutes.
Enter fullscreen mode Exit fullscreen mode

bindExamplecook1.png

想象一下,如果你不能在这里使用 bind 方法!这就像派对开始时你第一次做汉堡、鸡肉和牛排一样。你会毫无计划地向通用的 grill() 方法传入三个参数。

相反,我们使用偏函数应用来表明我们知道如何烹饪每种肉类。我们只需要了解每位客人想吃什么。这种拆分代表了您实际的烹饪经验。

Call 方法简介

再举个例子。假设你和伴侣准备一顿丰盛的晚餐时,你们通常喜欢搭配一些鱼和葡萄酒。正如你从第一段代码片段中看到的,你们通常喜欢用烤箱烤鱼。

但是,有一天晚上你决定做牛排。显然,你需要用烤架来做牛排。

牛排葡萄酒

问题在于:你的 grill()方法位于 cookout对象的上下文中!但现在,你想在 fancyDinner 对象中使用这些烹饪技能。记住,你不应该重写 grill 方法——这会使你的代码更难维护。

相反,您可以使用 JavaScript 的 call() 方法在fancyDinner对象的上下文中调用 grill 方法。使用这个新的上下文,您无需重写它。在我们深入细节之前,这里是完整的代码。

let cookout = {
  drink:"soda",
  grill: function(meal) {
   console.log("I am going to fire up the grill to cook " + meal + " with " +this.drink +" to drink!");
  }
} 

let fancyDinner = {
  drink: "wine",
  useOven: function() {}
}
Enter fullscreen mode Exit fullscreen mode

第一个Vcall方法

所以,我们户外野餐的默认饮品是苏打水,而高档晚宴的默认饮品是葡萄酒。现在,我们只需要在 call() 方法中添加一个不常见的参数—— “牛排”。以下是正常使用该方法和使用 call() 的区别。

cookout.grill("steak");
// "I am going to fire up the grill to cook steak with soda to drink!"

cookout.grill.call(fancyDinner, "steak");
// "I am going to fire up the grill to cook steak with wine to drink!"
Enter fullscreen mode Exit fullscreen mode

调用方法概要2.png

第一个例子应该很简单:一切都在 cookout 对象的上下文中。但在第二个例子中,第一个参数将this的上下文更改为fancyDinner对象!

当您进入 grill() 方法中的 console.log 语句时,您可以看到它引用了一个参数、meal以及this.drink。

当你使用 fancyDinner 作为 call 方法的第一个参数时,它会将上下文设置为 fancyDinner 对象。现在,你可以在另一个上下文中使用这些烧烤技能了。

Apply 方法简介

apply() 方法与 call() 非常相似,但有一个重要的
区别。它可以接受一个参数数组,而无需声明单个参数。这意味着您可以创建一个可变参数函数,即具有任意数量参数的函数。因此,它只能接受两个参数:上下文和一个参数数组。

让我们回到之前生日派对的例子。你正在为儿子或女儿的10岁生日举办户外烧烤派对。12个孩子回复说要去,但你不知道实际会有多少人到场。所以,你需要做好为人数不详的客人准备烧烤的准备。

但是,与 bind() 不同,使用 apply() 调用的函数将被立即调用。

因此,我们需要创建一个函数,用于处理包含未知数量餐点订单的数组,并返回需要放在烤架上的完整食物列表。我们可以保留数组的组织结构,这有助于我们确定请求的顺序。

let cookout = {
  mealOrders: ["chicken", "burger", "burger", "steak", "chicken"],
  grill: function() {
    let args = Array.prototype.slice.call (arguments);
    
    console.log("I am going to cook :" + args.join(","));
  } 
}
Enter fullscreen mode Exit fullscreen mode

这里有几点需要注意。首先,请注意 grill 方法没有任何参数。这和以前不同!

为了解决这个问题,我们在第 4 行使用参数对象。JavaScript中的参数对象为我们提供了一个充满函数参数的数组状对象。

要将其转换为实际数组,我们必须使用数组原型中的 slice() 方法。这是 call() 方法的另一个便捷应用,因为 slice() 方法并非对象原生的方法。

最后,我们必须使用apply()调用该函数,以便访问mealOrders属性中的数组。具体操作如下。

cookout.grill.apply(cookout, this.mealOrders);
// "I am going to cook: chicken, burger, burger, steak, chicken
Enter fullscreen mode Exit fullscreen mode

applymethod1.png

我们仍然必须使用cookout作为第一个参数,因为就像 call() 一样,我们必须声明执行上下文。然后,我们可以从 diningOrders 属性中传入数组。

由于我们可以将数组作为第二个参数传入,因此我们可以在 grill() 方法中使用不确定数量的元素。

获取最新教程

你喜欢这个教程吗?请在评论区留言告诉我,或者,你也可以在CodeAnalogies 博客上查看我其他的可视化教程

文章来源:https://dev.to/kbk0125/javascripts-apply-call-and-bind-explained-by-hosting-a-cookout-32jo
PREV
通过导演电影讲解 Node 包管理器 (NPM)
NEXT
通过制作圣代冰淇淋来解释 CSS 定位