В чем разница между заявкой и заявкой?


3106

В чем разница между использованием callи applyвызовом функции?

var func = function() {
  alert('hello!');
};

func.apply(); против func.call();

Есть ли различия в производительности между двумя вышеупомянутыми методами? Когда лучше использовать callснова applyи наоборот?


727
Подумайте о aприменении к массиву аргументов и cвызове столбцов с аргументами.
Ларри

176
@LarryBattle Я делаю почти то же самое, но я думаю, что применяются для массива и C в запятой (то есть аргументы, разделенные запятыми).
Самих

3
Я согласен, что это глупо. Что раздражает, так это то, что каким-то образом этот вопрос задают во время интервью, потому что какой-то влиятельный болван добавил вопрос в свой список важных вопросов.
Ринго

6
Вы подаете заявку на работу один раз (один аргумент), вы [ звоните по телефону] много раз (несколько аргументов). Альтернатива: [слишком?] Много игр Call of Duty.
Двойной Гра

1
Если намерение состоит в том, чтобы вызвать переменную функцию со списком значений аргумента независимо от значения «this», тогда используйте оператор расширения ES6, например, fn(...input)где input - это массив. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Gajus

Ответы:


3649

Разница в том, что applyвы можете вызывать функцию argumentsкак массив; callтребует, чтобы параметры были указаны явно. Полезный мнемонические является « для более rray и C для C OMMA.»

См. Документацию MDN о том, как подать заявку и позвонить .

Псевдосинтаксис:

theFunction.apply(valueForThis, arrayOfArgs)

theFunction.call(valueForThis, arg1, arg2, ...)

Существует также, как и в ES6, возможность spreadмассива для использования с callфункцией, вы можете увидеть совместимости здесь .

Образец кода:

function theFunction(name, profession) {
    console.log("My name is " + name + " and I am a " + profession +".");
}
theFunction("John", "fireman");
theFunction.apply(undefined, ["Susan", "school teacher"]);
theFunction.call(undefined, "Claude", "mathematician");
theFunction.call(undefined, ...["Matthew", "physicist"]); // used with the spread operator


25
Следует добавить, что аргументы должны быть числовым массивом ([]). Ассоциативные массивы ({}) не будут работать.
Кевин Шредер

326
@KevinSchroeder: на языке javascript, []называется массивом , {}называется объектом .
Мартин

89
Я часто забывал, что принимает массив, а кто ожидает, что вы перечислите аргументы. Техник Раньше я помню, если первая буква начинается с методом то он принимает массив т.е. в pply массива
Aziz punjani

16
@SAM Использование вызова вместо обычного вызова функции имеет смысл, только если вам необходимо изменить значение этого для вызова функции. Пример (который преобразует объект-аргументы функции в массив): Array.prototype.slice.call(arguments)или [].slice.call(arguments). Применение имеет смысл, если у вас есть аргументы в массиве, например, в функции, которая вызывает другую функцию с (почти) такими же параметрами. Рекомендация Используйте обычный вызов функции, funcname(arg1)если он делает то, что вам нужно, и сохраните вызов и подайте заявку в тех особых случаях, когда они вам действительно нужны.
некоторые

4
@KunalSingh Оба callи applyпринимает два параметра. Первый аргумент apply' and функции call` должен быть объектом-владельцем, а второй параметр будет соответственно параметром, разделенным массивом или запятой. Если вы передадите nullили в undefinedкачестве первого аргумента, то в нестрогом режиме они будут заменены глобальным объектом, т.е.window
AJ Qarshi

229

У К. Скотта Аллена хорошая статья по этому вопросу.

По сути, они отличаются тем, как они обрабатывают аргументы функции.

Метод apply () идентичен call (), за исключением того, что apply () требует массив в качестве второго параметра. Массив представляет аргументы для целевого метода. "

Так:

// assuming you have f
function f(message) { ... }
f.call(receiver, "test");
f.apply(receiver, ["test"]);

42
второй параметр apply () и call () является необязательным, необязательным.
злой киви

34
Первый параметр тоже не требуется.
Икром

@Ikrom, первый параметр не обязателен, callно обязателен apply
iamcastelli

160

Чтобы ответить на часть о том, когда использовать каждую функцию, используйте, applyесли вы не знаете, сколько аргументов вы будете передавать, или если они уже находятся в массиве или массивоподобном объекте (например, argumentsобъект для пересылки ваших собственных аргументов. Используйте callиначе, так как нет необходимости заключать аргументы в массив.

f.call(thisObject, a, b, c); // Fixed number of arguments

f.apply(thisObject, arguments); // Forward this function's arguments

var args = [];
while (...) {
    args.push(some_value());
}
f.apply(thisObject, args); // Unknown number of arguments

Когда я не передаю никаких аргументов (как ваш пример), я предпочитаю, callтак как я вызываю функцию. applyбудет означать, что вы применяете функцию к (несуществующим) аргументам.

Не должно быть никаких различий в производительности, за исключением, может быть, если вы используете applyи упаковываете аргументы в массив (например, f.apply(thisObject, [a, b, c])вместо f.call(thisObject, a, b, c)). Я не проверял это, поэтому могут быть различия, но это будет зависеть от браузера. Вероятно, callэто быстрее, если у вас еще нет аргументов в массиве, и applyбыстрее, если у вас есть.


111

Вот хорошая мнемоника. Pply использует A rrays и A сегда принимает один или два аргумента. Когда вы используете C все, что вам нужно, C Count количество аргументов.


2
Полезная мнемоника тут же! Я изменю «один или два аргумента» на «максимум два аргумента», так как не applyтребуется ни первый, ни второй параметры . Я не уверен, хотя, почему один будет вызывать applyили callбез параметра. Похоже, кто-то пытается выяснить, почему здесь stackoverflow.com/questions/15903782/…
Дантета

92

Хотя это старая тема, я просто хотел отметить, что .call немного быстрее, чем .apply. Я не могу сказать вам точно, почему.

См. JsPerf, http://jsperf.com/test-call-vs-apply/3


[ UPDATE!]

Дуглас Крокфорд кратко упоминает разницу между ними, что может помочь объяснить разницу в производительности ... http://youtu.be/ya4UHuXNygM?t=15m52s

Apply принимает массив аргументов, а Call принимает ноль или более отдельных параметров! Ах, ха!

.apply(this, [...])

.call(this, param1, param2, param3, param4...)


Это зависит от того, что функция делает с параметрами / массивом, если ей не нужно обрабатывать массив, это займет меньше времени?
Эрик Ходонский

12
Интересно, что даже без массива, вызов по-прежнему намного быстрее. jsperf.com/applyvscallvsfn2
Джош Мак

@JoshMc Это было бы очень специфично для браузера. В IE 11 я получаю заявку в два раза быстрее, чем call.
Винсент Макнабб

1
1. Создание нового массива означает, что сборщик мусора должен будет очистить его в какой-то момент. 2. Доступ к элементам в массиве с использованием разыменования менее эффективен, чем прямой доступ к переменной (параметру). (Я полагаю, что это то, что kmatheny имел в виду под «синтаксическим анализом», что на самом деле нечто совершенно иное.) Но ни один из моих аргументов не объясняет jsperf. Это должно быть связано с реализацией движком двух функций, например, возможно, они в любом случае создают пустой массив, если ни одна не была передана.
Joeytwiddle

Спасибо за то, что поделились тестом и видео
Гари

76

Далее следует выдержка из книги «Закрытие: полное руководство» Майкла Болина . Это может выглядеть немного длинным, но оно насыщено глубоким пониманием. Из «Приложения Б. Часто неверно понимаемые концепции JavaScript»:


Что thisотносится к тому, когда вызывается функция

При вызове функции формы foo.bar.baz()объект foo.barназывается получателем. Когда функция вызывается, это получатель, который используется в качестве значения для this:

var obj = {};
obj.value = 10;
/** @param {...number} additionalValues */
obj.addValues = function(additionalValues) {
  for (var i = 0; i < arguments.length; i++) {
    this.value += arguments[i];
  }
  return this.value;
};
// Evaluates to 30 because obj is used as the value for 'this' when
// obj.addValues() is called, so obj.value becomes 10 + 20.
obj.addValues(20);

Если нет явного получателя при вызове функции, тогда глобальный объект становится получателем. Как объясняется в «goog.global» на странице 47, окно - это глобальный объект, когда JavaScript выполняется в веб-браузере. Это приводит к неожиданному поведению:

var f = obj.addValues;
// Evaluates to NaN because window is used as the value for 'this' when
// f() is called. Because and window.value is undefined, adding a number to
// it results in NaN.
f(20);
// This also has the unintentional side effect of adding a value to window:
alert(window.value); // Alerts NaN

Несмотря на то, что они ссылаются на одну obj.addValuesи fту же функцию, при вызове они ведут себя по-разному, потому что значение получателя отличается при каждом вызове. По этой причине при вызове функции, которая ссылается this, важно убедиться, что она thisбудет иметь правильное значение при вызове. Чтобы было понятно, если бы thisне были ссылки в теле функции, то поведение f(20)и obj.addValues(20)было бы таким же.

Поскольку функции являются первоклассными объектами в JavaScript, они могут иметь свои собственные методы. Все функции имеют методы call()и apply()которые дают возможность переопределить приемник (то есть объект , который thisотносится к) при вызове функции. Подписи метода следующие:

/**
* @param {*=} receiver to substitute for 'this'
* @param {...} parameters to use as arguments to the function
*/
Function.prototype.call;
/**
* @param {*=} receiver to substitute for 'this'
* @param {Array} parameters to use as arguments to the function
*/
Function.prototype.apply;

Обратите внимание, что единственная разница между call()и apply()заключается в том, call()что параметры функции получают как отдельные аргументы, тогда как они apply()получают их как один массив:

// When f is called with obj as its receiver, it behaves the same as calling
// obj.addValues(). Both of the following increase obj.value by 60:
f.call(obj, 10, 20, 30);
f.apply(obj, [10, 20, 30]);

Следующие вызовы эквивалентны, как fи obj.addValuesотносятся к той же функции:

obj.addValues.call(obj, 10, 20, 30);
obj.addValues.apply(obj, [10, 20, 30]);

Тем не менее, поскольку ни один, call()ни другой не apply()используют значение своего собственного получателя для замены аргумента получателя, когда он не указан, следующее не будет работать:

// Both statements evaluate to NaN
obj.addValues.call(undefined, 10, 20, 30);
obj.addValues.apply(undefined, [10, 20, 30]);

Значение thisникогда не может быть nullили undefinedкогда функция вызывается. Когда nullили undefinedпредоставляется как получатель для call()или apply(), глобальный объект используется вместо значения для получателя. Следовательно, предыдущий код имеет тот же нежелательный побочный эффект, что и добавление свойства, названного valueглобальным объектом.

Может быть полезно думать о функции как о не имеющей знания о переменной, которой она назначена. Это помогает укрепить идею о том, что значение этого будет связано, когда функция вызывается, а не когда она определена.


Конец выписки.


Просто чтобы отметить тот факт, что additionalValuesне упоминается внутри obj.addValuesтела
Виктор Столбин

Я знаю, что вы отвечали на вопрос, но хотели бы добавить: вы могли бы использовать bind при определении f. var f = obj.addValues;становится var f = obj.addValues.bind(obj) и теперь f (20) будет работать без необходимости использовать вызов или применить каждый раз.
Jhliberty

Я знаю, что вы не написали это, но вы выделили текст и примеры из книги как соответствующие, и я очень благодарен. Они были очень полезны.
Фралкон

34

Иногда полезно, чтобы один объект заимствовал функцию другого объекта, а это означает, что заимствующий объект просто выполняет одолженную функцию, как если бы она была его собственной.

Небольшой пример кода:

var friend = {
    car: false,
    lendCar: function ( canLend ){
      this.car = canLend;
 }

}; 

var me = {
    car: false,
    gotCar: function(){
      return this.car === true;
  }
};

console.log(me.gotCar()); // false

friend.lendCar.call(me, true); 

console.log(me.gotCar()); // true

friend.lendCar.apply(me, [false]);

console.log(me.gotCar()); // false

Эти методы очень полезны для придания объектам временной функциональности.


1
Для людей, которые хотят знать, как посмотреть, console.logпосмотрите: что такое console.log и как его использовать?
Мишель Айрес

25

Еще один пример с Call, Apply и Bind. Разница между Call и Apply очевидна, но Bind работает так:

  1. Bind возвращает экземпляр функции, которая может быть выполнена
  2. Первый параметр « это »
  3. Второй параметр - это список аргументов через запятую (например, Call )

}

function Person(name) {
    this.name = name; 
}
Person.prototype.getName = function(a,b) { 
     return this.name + " " + a + " " + b; 
}

var reader = new Person('John Smith');

reader.getName = function() {
   // Apply and Call executes the function and returns value

   // Also notice the different ways of extracting 'getName' prototype
   var baseName = Object.getPrototypeOf(this).getName.apply(this,["is a", "boy"]);
   console.log("Apply: " + baseName);

   var baseName = Object.getPrototypeOf(reader).getName.call(this, "is a", "boy"); 
   console.log("Call: " + baseName);

   // Bind returns function which can be invoked
   var baseName = Person.prototype.getName.bind(this, "is a", "boy"); 
   console.log("Bind: " + baseName());
}

reader.getName();
/* Output
Apply: John Smith is a boy
Call: John Smith is a boy
Bind: John Smith is a boy
*/

23

Я хотел бы показать пример, где используется аргумент valueForThis:

Array.prototype.push = function(element) {
   /*
   Native code*, that uses 'this'       
   this.put(element);
   */
}
var array = [];
array.push(1);
array.push.apply(array,[2,3]);
Array.prototype.push.apply(array,[4,5]);
array.push.call(array,6,7);
Array.prototype.push.call(array,8,9);
//[1, 2, 3, 4, 5, 6, 7, 8, 9] 

** подробности: http://es5.github.io/#x15.4.4.7 *


20

Call () принимает аргументы через запятую, например:

.call(scope, arg1, arg2, arg3)

и apply () принимает массив аргументов, например:

.apply(scope, [arg1, arg2, arg3])

Вот еще несколько примеров использования: http://blog.i-evaluation.com/2012/08/15/javascript-call-and-apply/


`// call () === аргументы, разделенные запятыми (список аргументов) .call (this, args1, args2, args3, ...) // apply () === массив аргументов (array-items). применить (это, [arr0, arr1, arr2, ...]) `
xgqfrms

19

Из документов MDN на Function.prototype.apply () :

Метод apply () вызывает функцию с заданным thisзначением и аргументами, представленными в виде массива (или объекта, подобного массиву).

Синтаксис

fun.apply(thisArg, [argsArray])

Из документов MDN на Function.prototype.call () :

Метод call () вызывает функцию с заданным thisзначением и аргументами, предоставляемыми индивидуально.

Синтаксис

fun.call(thisArg[, arg1[, arg2[, ...]]])

Из Function.apply и Function.call в JavaScript :

Метод apply () идентичен call (), за исключением того, что apply () требует массив в качестве второго параметра. Массив представляет аргументы для целевого метода.


Пример кода:

var doSomething = function() {
    var arr = [];
    for(i in arguments) {
        if(typeof this[arguments[i]] !== 'undefined') {
            arr.push(this[arguments[i]]);
        }
    }
    return arr;
}

var output = function(position, obj) {
    document.body.innerHTML += '<h3>output ' + position + '</h3>' + JSON.stringify(obj) + '\n<br>\n<br><hr>';
}

output(1, doSomething(
    'one',
    'two',
    'two',
    'one'
));

output(2, doSomething.apply({one : 'Steven', two : 'Jane'}, [
    'one',
    'two',
    'two',
    'one'
]));

output(3, doSomething.call({one : 'Steven', two : 'Jane'},
    'one',
    'two',
    'two',
    'one'
));

Смотрите также эту скрипку .



10

Вот небольшой пост, я написал об этом:

http://sizeableidea.com/call-versus-apply-javascript/

var obj1 = { which : "obj1" },
obj2 = { which : "obj2" };

function execute(arg1, arg2){
    console.log(this.which, arg1, arg2);
}

//using call
execute.call(obj1, "dan", "stanhope");
//output: obj1 dan stanhope

//using apply
execute.apply(obj2, ["dan", "stanhope"]);
//output: obj2 dan stanhope

//using old school
execute("dan", "stanhope");
//output: undefined "dan" "stanhope"

вот еще один: blog.i-evaluation.com/2012/08/15/javascript-call-and-apply, но в принципе это правильно: .call (scope, arg1, arg2, arg3)
Марк Карвовски

7

Разница заключается в том, call()что аргументы функции принимаются отдельно, а apply()аргументы функции - в массиве.


6

Мы можем дифференцировать вызов и применять методы, как показано ниже

CALL: функция с аргументом обеспечивает индивидуально. Если вы знаете аргументы для передачи или нет аргументов для передачи, вы можете использовать call.

ПРИМЕНИТЬ: вызвать функцию с аргументом, представленным в виде массива. Вы можете использовать apply, если вы не знаете, сколько аргументов будет передано функции.

Есть преимущество использования apply over call, нам не нужно изменять количество аргументов, только мы можем изменить передаваемый массив.

Там нет большой разницы в производительности. Но мы можем сказать, что вызов немного быстрее, чем сравнение для применения, потому что массив должен оцениваться в методе apply.


5

Разница между этими методами заключается в том, как вы хотите передать параметры.

«A для массива и C для запятой» - удобная мнемоника.


11
Что дает этот ответ, который еще недостаточно хорошо представлен в других ответах?
Kyll

5

Вызов и применение обоих используются для принудительного вызова thisзначения при выполнении функции. Единственная разница в том, что callпринимает n+1аргументы, где 1 thisи 'n' arguments. applyпринимает только два аргумента, один thisиз которых является массивом аргументов.

Преимущество я вижу в applyболее чем в callтом , что мы можем легко передать вызов функции другой функции без особых усилий;

function sayHello() {
  console.log(this, arguments);
}

function hello() {
  sayHello.apply(this, arguments);
}

var obj = {name: 'my name'}
hello.call(obj, 'some', 'arguments');

Обратите внимание , как легко мы делегировали helloв sayHelloиспользовании apply, но с callэтим очень трудно достичь.


4

Несмотря на то, callи applyachive то же самое, я думаю , что есть по крайней мере одно место , где вы не можете использовать , callно может использовать только apply. Это когда вы хотите поддерживать наследование и хотите вызвать конструктор.

Вот функция, позволяющая создавать классы, которая также поддерживает создание классов путем расширения других классов.

function makeClass( properties ) {
    var ctor = properties['constructor'] || function(){}
    var Super = properties['extends'];
    var Class = function () {
                 // Here 'call' cannot work, only 'apply' can!!!
                 if(Super)
                    Super.apply(this,arguments);  
                 ctor.apply(this,arguments);
                }
     if(Super){
        Class.prototype = Object.create( Super.prototype );
        Class.prototype.constructor = Class;
     }
     Object.keys(properties).forEach( function(prop) {
           if(prop!=='constructor' && prop!=='extends')
            Class.prototype[prop] = properties[prop];
     });
   return Class; 
}

//Usage
var Car = makeClass({
             constructor: function(name){
                         this.name=name;
                        },
             yourName: function() {
                     return this.name;
                   }
          });
//We have a Car class now
 var carInstance=new Car('Fiat');
carInstance.youName();// ReturnsFiat

var SuperCar = makeClass({
               constructor: function(ignore,power){
                     this.power=power;
                  },
               extends:Car,
               yourPower: function() {
                    return this.power;
                  }
              });
//We have a SuperCar class now, which is subclass of Car
var superCar=new SuperCar('BMW xy',2.6);
superCar.yourName();//Returns BMW xy
superCar.yourPower();// Returns 2.6

Я считаю, что вызов будет работать там с оператором распространения, как описано в выбранном ответе. Если я что-то упустил.
Jhliberty

4

Резюме:

Оба call()и apply()являются методами, которые расположены на Function.prototype. Поэтому они доступны для каждого объекта функции через цепочку прототипов. Оба call()и apply()могут выполнять функцию с указанным значением this.

Основное различие между call()и apply()заключается в том, как вы должны передавать аргументы в него. В обоих случаях, call()и apply()вы передаете в качестве первого аргумента объект , который вы хотите быть значение , как this. Другие аргументы отличаются следующим образом:

  • При этом call()вы должны поставить аргументы как обычно (начиная со второго аргумента)
  • С apply()вами нужно передать массив аргументов.

Пример:

let obj = {
  val1: 5,
  val2: 10
}

const summation = function (val3, val4) {
  return  this.val1 + this.val2 + val3 + val4;
}

console.log(summation.apply(obj, [2 ,3]));
// first we assign we value of this in the first arg
// with apply we have to pass in an array


console.log(summation.call(obj, 2, 3));
// with call we can pass in each arg individually

Зачем мне нужно использовать эти функции?

thisЗначение может быть сложно иногда в JavaScript. Значение thisопределяется, когда функция выполняется, а не когда функция определена. Если наша функция зависит от правильной thisпривязки, мы можем использовать это call()и apply()применять его. Например:

var name = 'unwantedGlobalName';

const obj =  {
  name: 'Willem',
  sayName () { console.log(this.name);}
}


let copiedMethod = obj.sayName;
// we store the function in the copiedmethod variable



copiedMethod();
// this is now window, unwantedGlobalName gets logged

copiedMethod.call(obj);
// we enforce this to be obj, Willem gets logged


4

Основное отличие заключается в том, что с помощью вызова мы можем изменить область и передать аргументы как обычно, но применять позволяет вам вызывать его, используя аргументы в качестве массива (передавая их как массив). Но с точки зрения того, что они должны делать в вашем коде, они довольно похожи.

Хотя синтаксис этой функции практически идентичен синтаксису метода apply (), принципиальное отличие состоит в том, что call () принимает список аргументов, а apply () принимает один массив аргументов.

Итак, как видите, нет большой разницы, но все же есть случаи, когда мы предпочитаем использовать call () или apply (). Например, посмотрите на приведенный ниже код, который находит наименьшее и наибольшее число в массиве из MDN, используя метод apply:

// min/max number in an array
var numbers = [5, 6, 2, 3, 7];

// using Math.min/Math.max apply
var max = Math.max.apply(null, numbers); 
// This about equal to Math.max(numbers[0], ...)
// or Math.max(5, 6, ...)

var min = Math.min.apply(null, numbers)

Таким образом, главное отличие заключается в том, как мы передаем аргументы:

Call:

function.call(thisArg, arg1, arg2, ...);

Применять:

function.apply(thisArg, [argsArray]);

2

Позвольте мне добавить немного деталей к этому.

эти два вызова почти эквивалентны:

func.call(context, ...args); // pass an array as list with spread operator

func.apply(context, args);   // is same as using apply

Там только небольшая разница:

  • spreadОператор ... позволяет перейти Iterable args в списке , чтобы позвонить.
  • applyПринимает только массив типа арг.

Итак, эти звонки дополняют друг друга. Там, где мы ожидаем повторяемость , callработает, где мы ожидаем, как массив , applyработает.

А для объектов, которые являются итеративными и похожими на массивы , например реальный массив, мы технически могли бы использовать любой из них, но применение , вероятно, будет быстрее, потому что большинство движков JavaScript внутренне оптимизируют его лучше.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.