在本文中,我将解释 call
、apply
和 bind
方法以及如何编写它们的 polyfill
。这三个 polyfill
是 JavaScript
面试中非常常见的问题。
让我们从一个例子开始,了解这些方法的必要性,然后我们将讨论它们的实现。
看看下面的代码,我们有一个 person 对象和一个 printName 方法。
1 | let person = { |
如你所见,person 对象是非常通用的对象,我们可以拥有多个具有不同值的相同类型对象。在这里,我在 printName 方法中使用了这个关键字。如果你不熟悉它。别担心。我们稍后会介绍它。
现在,我们要为每个 person 对象执行一个 printName 方法。
我们该怎么做?
第一个选项是,我们将 printName 方法添加到每个对象并调用它,如下所示。
1 | let person = { |
如果你看到上面的代码,你会发现我们为每个对象重复了 printName 方法。这似乎不是一个好的做法。这就是我在第一个代码块中将 printName 方法定义为单独方法的原因。
现在,怎么办?
Javascript
为我们提供了三种方法来处理这种情况,而无需重复代码。
- call(object,arguments) - 如果有传递的对象以及传递的参数,则调用该函数
- apply(object,[arguments]) - 如果有传递的对象以及传递的参数数组,则调用该函数
- bind(object,arguments) - 返回一个引用传递的对象和参数的新函数
让我们首先从方法开始。
call 方法
call
方法通过将要执行该方法的对象作为第一个参数来调用函数,并接受可在该方法中传递的参数,例如 printName 方法中的 country。
它等于 person.printName(“China”)。在 printName 方法中,此关键字指的是 person 对象。因为这总是指调用该方法的 . 的左侧。你可以查看此链接以了解有关此关键字的更多信息。
1 | let person = { |
在这里,我们将调用方法附加到 printName 函数,其中 printName 方法被 person 对象调用,因此它从中获取 firstName 和 lastName 的值,并将“China”作为 country 参数的参数,并产生上述输出。
apply 方法
apply
方法与 call
方法非常相似,但唯一的区别是 call
方法将参数作为逗号分隔的值,而 apply
方法则采用参数数组。如果我们编写上述代码的 apply
方法实现。它将是这样的。
1 | let person = { |
在这里,我们用 apply
方法替换了 call
并将参数传递到数组中。
让我们转到最后一种方法。
bind 方法
bind
方法与 call
方法类似,但唯一的区别是 call
方法调用函数,而 bind
方法返回一个可以稍后调用的新函数。让我们实现 bind
方法。
1 | let person = { |
如果你看到上面的代码,我们已将 bind
方法附加到 printName 并将其存储到名为 newPrintName 的新变量中。
现在,我们可以在代码中随时调用 newPrintName,它将产生相同的输出。
polyfill 实现
现在,让我们为所有三种方法编写 polyfill
。在多姿多彩的 JavaScript
世界,polyfill
如同一座架在浏览器兼容性鸿沟之上的桥梁,让新生的 Web 特性得以在陈旧浏览器中生根发芽。
我将使用 Javascript
原型继承来编写 pollyfill
。使 pollyfill
可用于所有函数。
如果你查看所有三种方法。它们应用于函数,因此我们将我们的 polyfill
方法添加到函数原型。
你可以在此处阅读有关原型的信息。
让我们从调用方法 polyfill
开始。
1 | let person = { |
在这里,我使用 Function.prototype
使 mycall 方法可用于所有函数并为其分配一个新函数。
与调用方法相同,mycall 方法将要在其上调用该方法的对象作为第一个参数,然后将其余参数传递给函数。
现在,让我们了解该函数的内部实现。
在 mycall 函数内部,我们创建了一个符号 sym,以在传递的对象上创建一个唯一属性,以防止现有属性被覆盖。
现在,在传递的对象上添加 sym 属性并为其分配 this
关键字。它引用 printName 函数。
在下一行中,我们通过传递剩余的参数来调用该函数并将其响应存储在一个新变量 res 中。此后,我们从传递的对象中删除新创建的属性 sym,因为它不存在于该函数之外的对象上,然后我们返回对象的响应。
所以,我们最终创建了第一个 polyfill
,结果相同。
让我们跳到应用方法 polyfill
。
正如我们所见,apply
与 call
方法非常相似,只是它接受的是参数数组,而不是以逗号分隔的参数列表。因此,apply
的实现与 call
方法相同,只有细微的差别。
1 | let person = { |
如果你看到上面的代码,则实现步骤是相同的 ,但是当我们在对象上调用函数而不是直接将 ...args
作为参数传递时,我们将使用 rest 运算符传递 args
的第 0 个索引,因为 rest 运算符 ‘…’ 表示参数数组,并且在此数组中,我们在第 0 个索引处传递了参数数组,因此将选择该数组并将其扩展到函数中。
让我们编写最终的 bind
方法 polyfill
。
如果我们回想一下 bind
方法的实现。我们知道它与调用相同,但不是调用函数而是返回新函数。让我们看看实现。
1 | let person = { |
这里,与 mycall 和 myapply 方法相同。我们在 Function.prototype
上创建了一个 mybind 方法并为其分配了一个函数。此函数接受与 bind 方法类似的对象和参数。我们已经从上面的 polyfill
实现中知道 this
关键字引用该函数。在 bind 的情况下,我们将 this
存储在名为 func 的变量中。因为 bind 返回一个新函数。我们还将返回一个匿名函数,它将充当 mybind 函数中的闭包。现在,这个返回函数还可以接受参数,这些参数将表示在调用 mybind 方法返回的新函数期间传递的参数。在这个返回函数中,我们将对 func 变量使用 apply 方法,以便为传递的对象和参数调用它。在这个 apply 方法中,我们将创建一个数组来传递参数,在这个数组中,我们将展开 args 和 args1 以将所有参数传递给函数并将其存储在新变量 newPrintName 中。
稍后,当我们调用这个 newPrintName 时。结果相同。如果我们在 newPrintName 函数中传递任何参数,args1 将在 mybind 方法中表示这些参数。
这就是有关 call
、apply
、bind
及其 polyfill
的全部内容。
请分享你对本文的评论和反馈。
祝你学习愉快!