链式调用和apply一起使用的含义是什么?

8

我在jsGarden中遇到了这段代码,但是我无法理解为什么要将callapply链接在一起。它们都可以使用给定的上下文对象执行函数,为什么可以链接呢?

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// Create an unbound version of "method" 
// It takes the parameters: this, arg1, arg2...argN
Foo.method = function() {

    // Result: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

1
我非常确定你在.call之前忘记了一个prototype - Bergi
1
@Bergi:实际上没有区别。因为Function是一个函数,它从自己的原型中继承了call - Ventero
@Ventero:哦,对了,我忘记了那个…… - Bergi
4个回答

12

它通过apply调用call;也就是说,它使用call来调用一个函数("方法"),并且使用apply进行调用,因为它的参数以几乎数组的形式存在。

因此,将其拆分:

Function.call

这是指所有函数实例都可以继承自Function原型中提供的call()函数。

Function.call.apply

这是一个通过对call函数的引用来引用apply的参考。 因为apply是通过call对象引用的,所以当调用apply时,this值将是对call函数的引用。

Function.call.apply(Foo.prototype.method, arguments);

我们通过 apply 调用了 call 函数,将 Foo.prototype.method 作为 this 值传递,并将 "Foo.mmethod" 的参数作为参数。

我认为这基本上与以下代码的效果相同:

Foo.method = function() {
  var obj = arguments[0], args = [].slice.call(arguments, 1);
  Foo.prototype.method.apply(obj, args);
}

但我必须尝试一下才能确定。编辑是的,似乎就是这样。所以我可以总结这个技巧的要点是当期望的this值是包含参数的数组的第一个元素时,调用apply()的方式。换句话说,通常情况下,当你调用apply()时,你已经有了期望的this对象引用和参数(在一个数组中)。然而,在这里,由于想法是将期望的this作为参数传入,因此它需要被分离出来以进行apply的调用。就我个人而言,我会像我的“翻译”中那样做,因为这样比较容易理解,但我想一个人也可以习惯它。在我的经验中,这不是一个常见的情况。


3
此外,这强制使用 Function.callFunction.prototype.apply,以防所提供的函数已经覆盖了其 call 方法。 - zzzzBov
@Utkanos 我也在苦思冥想这样做的目的是什么;我知道正在发生什么,但我不明白为什么不能在切掉第一个参数后直接调用Foo.prototype.method.call() - Pointy
1
@Utkanos,通常这种晦涩的用法是由库使用的,它尽力强制正确地使用正确的方法,无论JS中添加了什么垃圾。 - zzzzBov
@Pointy - 是的,那也是我的想法。很高兴知道我不是一个人 :) - Mitya
读了几遍之后,终于明白了。这个用法真的很难懂,我大多数时候都是根据你写的方式手动绑定的。 - steveyang
显示剩余2条评论

2
我认为代码应该像这样:

我认为代码应该像这样:

function Foo() {}

Foo.prototype.method = function(a, b, c) {
 console.log(this, a, b, c);
};

Foo.method = function() {

 //Notice this line:
 Function.apply.call(Foo.prototype.method, this, arguments);
};

那么

Foo.method(1,2,3) => function Foo() {} 1 2 3

其他例子:
Function.apply.call(Array,this,[1,2]) => [1, 2]
Function.call.apply(Array,this,[1,2]) => [window]
Function.call.call(Array,this,[1,2])  => [[1, 2]]

1
Person.prototype.fullname = function(joiner, options) {
  options = options || { order: "western" };
  var first = options.order === "western" ? this.first : this.last;
  var last =  options.order === "western" ? this.last  : this.first;
  return first + (joiner || " ") + last;
};

// Create an unbound version of "fullname", usable on any object with 'first'
// and 'last' properties passed as the first argument. This wrapper will
// not need to change if fullname changes in number or order of arguments.
Person.fullname = function() {
  // Result: Person.prototype.fullname.call(this, joiner, ..., argN);
  return Function.call.apply(Person.prototype.fullname, arguments);
};

来自Javascript Garden的代码。

注意,它声明了

Function.call.apply(Person.prototype.fullname, arguments);
将变成这个样子:

Person.prototype.fullname.call(this, joiner, ..., argN);

它意味着apply()函数会先执行,然后是call()函数。

模式:最右侧call() / apply()会先被执行

  1. 因此,最右侧apply()会先执行
  2. apply()的上下文成为call()函数的调用者,所以现在是
    Person.prototype.fullname.call()
  3. apply()只能使用一个单一数组的参数,所以apply()提供argumentscall()函数,现在是
    Person.prototype.fullname.call(arguments)

来自@foxiris的示例:

第一个:

Function.apply.call(Array,this,[1,2])
  1. 最右边的 call() 将首先被执行
  2. call() 的上下文成为 apply() 的调用者,因此现在是 Array.apply()
  3. call() 可以接受多个参数,因此它可以向 apply() 提供 this[1, 2],所以现在是 Array.apply(this, [1, 2]);,这将输出 [1, 2]

第二个:

Function.call.apply(Array,this,[1,2])
  1. 最右边的 apply() 将首先被执行。
  2. apply() 的上下文变成了 call() 的调用者,所以现在是 Array.call()
  3. apply() 只能接受一个单一的数组参数,所以它只能将 this 提供给 call(),所以现在是 Array.call(this);,输出是 []

第三个:

Function.call.call(Array,this,[1,2])
  1. 最右边的 call() 将首先被执行。
  2. call() 的上下文(最右边的)成为 call() 的调用者(右数第二个),因此现在是 Array.call()
  3. call() 可以接受多个参数,因此它可以将 this[1, 2] 提供给另一个 call(),因此现在是 Array.call(this, [1, 2]);,输出是 [[1, 2]]

1

apply 函数的第二个参数可以是一个数组,而 call 函数只能接受单个参数。

 // lets take call,
 var callfn = Function.prototype.call;
 // an ordinary function from elsewhere
 var method = Foo.prototype.method;
 // and apply the arguments object on it:
 callfn.apply(method, arguments);

所以,第一个arguments项将是methodthis值,随后将填充单个参数。

结果是在Foo构造函数上创建了一个静态函数method,它将Foo实例(或类似物)作为第一个参数,并在其上应用原型method。一个可能的用例是定义一个Object.hasOwnProperty函数,这通常只能作为Object.prototype.hasOwnProperty使用。

最终,如果您需要将其应用于a)不继承它或b)覆盖它的对象,则使method的调用变得更短,只需一个“原型”和一个“调用”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接