如何在JavaScript中使用call和apply的上下文?

75

各位能否解释一下在Javascript中使用callapply方法的上下文?

为什么要使用callapply而不是直接调用函数?


3
这篇阅读材料帮助我理解了在调用 apply()call()thisArg 的作用,而这似乎是你提问的核心所在。你需要理解 JavaScript 中的函数调用原语。 - Saba Ahang
6个回答

83

如果你想要将一个不同的this值传递给函数,你需要使用call或者apply。换句话说,这意味着你想要像执行特定对象的方法一样执行一个函数。两者之间唯一的区别是call期望用逗号分隔的参数,而apply期望使用数组形式的参数。

以下是来自Mozilla的apply页面的示例,展示如何链接构造器:

function Product(name, price) {
    this.name = name;
    this.price = price;

    if (price < 0)
        throw RangeError('Cannot create product "' + name + '" with a negative price');
    return this;
}

function Food(name, price) {
    Product.apply(this, arguments);
    this.category = 'food';
}
Food.prototype = new Product();

function Toy(name, price) {
    Product.apply(this, arguments);
    this.category = 'toy';
}
Toy.prototype = new Product();

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

Product.apply(this, arguments) 的作用是:在每个 FoodToy 构造函数内部将 Product 构造函数作为函数应用,并将每个对象实例作为 this 参数传递。因此,现在每个 FoodToy 都有 this.namethis.category 属性。


6
关于提供的代码片段,为什么Toy和Food的原型会被Product对象所替换? - davids
只是评论一下,以提醒@davids的问题:为什么我们需要执行Toy.prototype = new Product();?我测试了一下,删除这些代码似乎对最终结果没有影响。 - adrianp
6
@davids和@adrianp: 需要让食品(Food)和玩具(Toy)继承自产品(Product)。请看这个例子:http://jsfiddle.net/Shawn/xNEYf/。我删除了`Toy.prototype = new Product();,但保留了Food.prototype = new Product();`,所以现在食品继承自产品,但是玩具没有继承。我还向产品的原型添加了一个方法,并从食品和玩具中调用它,以使缺少继承更加明显(只需打开控制台并运行代码,食品可以调用从产品继承来的方法,但是玩具无法调用并抛出错误)。 - Shawn
当使用对象字面量时,原型如何被使用? - user244394
我想我只是不明白为什么要这样做,而不是像这样那样看起来更容易的方式。 - user4531029
@Shawn如果您不添加一个产品的原型并删除Toy.prototype = new Product();Food.prototype = new Product();,它会正常工作,那么为什么要在示例中添加这些行。 - node_saini

23

只有使用callapply,才能在函数内修改this上下文。

与其他语言不同,JavaScript中的this并不指向当前对象,而是执行上下文,并且可以由调用者设置。

如果使用new关键字调用函数,则this将正确地引用新对象(在构造函数内部)。

但在所有其他情况下,除非通过调用显式设置,否则this将引用全局对象。


如果使用obj.whatever();语法,它可能会引用调用者对象。 - code
语句“But in all other cases - this will refer to the global object unless set explicitly through call”应更正为以下内容:“但在所有其他情况下,除非通过调用显式设置,否则如果在非严格模式下使用Ecmascript 3或Ecmascript 5(或更高版本),this将引用全局对象。然而,在Ecmascript 5(或更高版本)的严格模式下,如果未明确设置并访问this,则始终为undefined,并且JS引擎将抛出异常。” 参考:https://dev59.com/OWkw5IYBdhLWcg3wi7Fy - Alan C. S.

12
当你想要使用不同的this值来执行函数时,可以使用.call()。它将指定的this值设置为指定值,设置参数,然后调用函数。与直接执行函数的区别在于函数执行时this指针的值。当您正常执行函数时,JavaScript决定this指针将是什么(通常是全局上下文window,除非该函数作为对象的方法调用)。当您使用.call()时,您可以精确指定this的设置值。
当您要传递到函数中的参数在数组中时,可以使用.apply().apply()也可以使函数使用特定的this值执行。当您有不确定数量的参数来自其他来源时,通常使用.apply()。它经常被用来通过使用特殊的本地变量arguments来将一个函数调用的参数传递给另一个函数。
我发现MDN对.call().apply()的参考页面很有帮助。

谢谢,你讲得非常好。终于明白了第一个参数的用法。 - Question Overflow

4
如果你有jQuery经验,你会知道大多数函数使用this对象。例如:collection.each(function() { ... });在这个函数中,"this"指的是迭代器对象。这是一种可能的用法。
我个人使用.apply()来实现请求队列——将参数数组推入队列中,当执行时,我取出一个元素,并使用.apply()将其作为处理程序函数的参数传递,这样代码就比传递参数数组作为第一个参数更清晰了。这是另一个例子。
总的来说,只要记住这些调用函数的方式存在,你可能会在实现程序时发现它们很方便。

1
如果您有面向对象编程的经验,那么将call和apply与继承进行比较,并从子类重写父类的属性或方法/函数,这将使它更加容易理解。这与JavaScript中的调用类似,如下所示:
function foo () {
  this.helloworld = "hello from foo"
}
foo.prototype.print = function () {
  console.log(this.helloworld)
}

foo.prototype.main = function () {
  this.print()
}


function bar() {
  this.helloworld = 'hello from bar'
}
// declaring print function to override the previous print
bar.prototype.print = function () {
  console.log(this.helloworld)
}

var iamfoo = new foo()

iamfoo.main() // prints: hello from foo

iamfoo.main.call(new bar()) // override print and prints: hello from bar

0

我想不出任何正常情况下将thisArg设置为其他内容是使用apply的目的。

apply的目的是将值数组传递给需要这些值作为参数的函数。

在所有常规日常用法中,它已被展开运算符取代。

例如:

// Finding the largest number in an array
`Math.max.apply(null, arr)` becomes `Math.max(...arr)`

// Inserting the values of one array at the start of another
Array.prototype.unshift.apply(arr1, arr2); 
// which becomes 
arr1 = [...arr2, ...arr1]

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