Яка різниця між дзвінками та заявкою?


3103

Яка різниця між використанням callта applyвикликом функції?

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

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

Чи існують відмінності між двома вищезазначеними методами? Коли найкраще використовувати callпонад applyі навпаки?


727
Подумайте про те, aзастосуйте для масиву args та cзателефонуйте до стовпців.
Битва Ларрі

176
@LarryBattle я роблю майже те саме, але я думаю, що a застосовується для масиву та c для виклику коми (тобто аргументи, розділені комами).
Саміх

3
Я згоден, це дурно. Що дратує те, що якось це питання задають під час інтерв'ю, оскільки якийсь впливовий кумед додав це питання до свого списку важливих питань js.
Рінго

6
Ви подаєте заявку на роботу один раз (один аргумент), ви [телефон] телефонуєте людям багато разів (кілька аргументів). Альтернатива: є [занадто?] Багато ігор Call of Duty.
Подвійний

1
Коли наміром є виклик варіативної функції зі списком значень аргументів незалежно від значення "це", тоді використовуйте оператор розширення ES6, наприклад, fn(...input)де вхід - масив. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Gajus

Відповіді:


3647

Різниця полягає в тому, що 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 []називається масив , {}називається об'єктом .
Martijn

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

16
@SAM Використання виклику замість звичайного виклику функції має сенс лише у тому випадку, якщо вам потрібно змінити значення цього для виклику функції. Приклад (який перетворює функції-аргументи-об'єкт у масив): Array.prototype.slice.call(arguments)або [].slice.call(arguments). застосовувати має сенс, якщо у вас є аргументи в масиві, наприклад у функції, яка викликає іншу функцію з (майже) тими ж параметрами. Рекомендація Використовуйте звичайний функціональний дзвінок, funcname(arg1)якщо він робить те, що вам потрібно, і збережіть дзвінок і подайте заявку на ті особливі випадки, коли вони вам справді потрібні.
десь

4
@KunalSingh Обидва callі applyприймає два параметри. Перший аргумент apply' and функції виклику повинен бути об'єктом власника, а другий параметр буде параметри масиву або розділені комами відповідно. Якщо ви передаєте 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
другий параметр застосовувати () і 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 не має кількості аргументів.


2
Корисне мнемонічне саме там !. Я зміню "один або два аргументи", щоб сказати "максимум два аргументи", оскільки ні перший, ні другий параметри applyне потрібні. Я не впевнений, хоча чому виклик applyабо callбез параметра. Схоже , хто - то намагається з'ясувати , чому тут stackoverflow.com/questions/15903782 / ...
dantheta

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 я отримую заявку вдвічі швидше, ніж дзвінок.
Вінсент Макнабб

1
1. Створення нового масиву означає, що сміттєзбірник потребує його очищення в якийсь момент. 2. Доступ до елементів масиву з використанням дереференції є менш ефективним, ніж прямий доступ до змінної (параметра). (Я вважаю, що це означає, що kmatheny означає "розбір", що насправді є зовсім іншим.) Але жоден з моїх аргументів не пояснює jsperf. Це повинно бути пов'язано з реалізацією двох функцій двигуном, наприклад, можливо, вони все одно створюють порожній масив, якщо жодна не була передана.
joeytwiddle

Дякую за те, що ви поділилися тестом та відео
Gary

76

Дотримується витяг із закриття: Посібник з остаточного значення Майкла Боліна . Це може виглядати трохи затяжно, але воно насичене багато розуміння. З "Додаток B. Часто невідомі поняття 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

Я знаю, що ви цього не написали, але ви виділили текст та приклади книги як відповідні, і я дуже вдячний. Вони були дуже корисні.
Fralcon

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 and 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 (це, 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 (область, arg1, arg2, arg3)
Марк Karwowski

7

Різниця полягає в тому, що call()приймає аргументи функції окремо і apply()приймає аргументи функції в масив.


6

Ми можемо диференціювати методи виклику та застосовувати, як показано нижче

CALL: Функцію з аргументом надайте індивідуально. Якщо ви знаєте аргументи, які потрібно передавати, або немає аргументу для передачі, ви можете використовувати call.

ЗАСТОСУЙТЕ: виклик функції з аргументом, наданим у вигляді масиву. Ви можете скористатись застосувати, якщо ви не знаєте, скільки аргументів буде передано функції.

Є перевага використання застосунку над викликом, нам не потрібно змінювати кількість аргументів, ми можемо змінити переданий масив.

Немає великої різниці в продуктивності. Але ми можемо сказати, що виклик є трохи швидшим порівняно із застосуванням, оскільки масив повинен оцінювати метод застосування.


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і applyтого ж, я думаю, що є принаймні одне місце, де ви не можете користуватися, 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

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

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

Отже, як бачите, немає великої різниці, але все ж, є випадки, коли ми вважаємо за краще використовувати call () або Apply (). Наприклад, подивіться на код нижче, який знаходить найменше і найбільше число в масиві з MDN, використовуючи метод застосовування:

// 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.