Функция Javascript как параметр для другой функции?

В наши дни я изучаю много javascript, и одна из вещей, которые я не совсем понимаю, передаёт функции как параметры другим функциям. Я получаю представление о таких вещах, но сам я не могу придумать ситуации, в которых это было бы идеально.

Мой вопрос:

Когда вы хотите, чтобы ваши функции javascript использовали другую функцию в качестве параметра? Почему бы просто не назначить переменную этой функции возвращаемое значение и передать эту переменную такой функции:

// Why not do this
var foo = doStuff(params);
callerFunction(foo);

//instead of this
callerFunction(doStuff);

Я смущен тем, почему я когда-либо хотел делать что-то, как в моем втором примере.

Зачем вам это делать? Каковы некоторые варианты использования?

Спасибо!!

Ответ 1

Для этого есть несколько вариантов использования:

1. Функции Wrapper.

Допустим, у вас есть куча разных битов кода. До и после каждого бит кода вы хотите сделать что-то еще (например: log или try/catch exceptions).

Вы можете написать функцию "Wrapper", чтобы справиться с этим. EG:

function putYourHeadInTheSand(otherFunc) {
    try{
         otherFunc();
    } catch(e) { } // ignore the error
}

....

putYourHeadInTheSand(function(){
    // do something here
});
putYourHeadInTheSand(function(){
    // do something else
});

2. Callbacks.

Предположим, вы каким-то образом загрузили некоторые данные. Вместо того, чтобы блокировать систему, ожидающую ее загрузки, вы можете загрузить ее в фоновом режиме и сделать что-то с результатом, когда она придет.

Теперь, как вы узнаете, когда он придет? Вы можете использовать что-то вроде сигнала или мьютекса, который трудно написать и уродливый, или вы можете просто сделать функцию обратного вызова. Вы можете передать этот обратный вызов функции Loader, которая может вызвать ее, когда она будет выполнена.

Каждый раз, когда вы делаете XmlHttpRequest, это в значительной степени то, что происходит. Вот пример.

function loadStuff(callback) {
    // Go off and make an XHR or a web worker or somehow generate some data
    var data = ...;
    callback(data);
}

loadStuff(function(data){
    alert('Now we have the data');
});

3. Генераторы/итераторы

Это похоже на обратные вызовы, но вместо однократного вызова callback вы можете называть его несколько раз. Представьте, что функция загрузки данных не просто загружает один бит данных, может быть, она загружает 200.

Это в конечном итоге очень похоже на цикл for for/foreach, за исключением асинхронного. (Вы не ждете данных, он вызывает вас, когда он готов).

function forEachData(callback) {
    // generate some data in the background with an XHR or web worker
    callback(data1);
    // generate some more data in the background with an XHR or web worker
    callback(data2);
    //... etc
}

forEachData(function(data){
    alert('Now we have the data'); // this will happen 2 times with different data each time
});

4. Ленивая загрузка

Позволяет сказать, что ваша функция что-то делает с некоторым текстом. НО нужен только текст, возможно, один раз из 5, и текст может быть очень дорогим для загрузки.

Итак, код выглядит так:

var text = "dsakjlfdsafds"; // imagine we had to calculate lots of expensive things to get this.
var result = processingFunction(text);

Функция обработки на самом деле нуждается в тексте в 20% случаев! Мы потратили впустую все эти усилия, загрузив их в это дополнительное время.

Вместо передачи текста вы можете передать функцию, которая генерирует текст, например:

var textLoader = function(){ return "dsakjlfdsafds"; }// imagine we had to calculate lots of expensive things to get this.
var result = processingFunction(textLoader);

Вам придется изменить processingFunction, чтобы ожидать другую функцию, а не текст, но это действительно незначительно. Теперь происходит то, что processingFunction будет вызывать только textLoader в 20% случаев, когда он ему нужен. Другие 80% времени, он не будет вызывать функцию, и вы не будете тратить на это все усилия.

4а. Кэширование

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

Код, который вызывает textLoader, не знает и не заботится о том, чтобы данные кэшировались, он прозрачно только быстрее.

Есть много более продвинутых вещей, которые вы можете сделать, передавая функции, это просто царапает поверхность, но, надеюсь, она указывает вам в правильном направлении: -)

Ответ 2

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

Вот код такой функции:

function map(arr, func) {
    for (var i = 0; i < arr.length; ++i) {
        arr[i] = func(arr[i]);
    }
}

Примером использования может быть умножение каждого элемента в массиве на 2:

var numbers = [1, 2, 3, 4, 5];
map(numbers, function(v) {
    return v * 2;
});

// numbers now contains 2, 4, 6, 8, 10

Ответ 3

Вы сделали бы это, если callerFunction хочет вызвать doStuff позже, или если он хочет называть его несколько раз.

Типичным примером этого использования является функция обратного вызова, в которой вы передаете обратный вызов функции типа jQuery.ajax, которая затем вызывает ваш обратный вызов, когда что-то закончит (например, запрос AJAX)

EDIT. Чтобы ответить на ваш комментарий:

function callFiveTimes(func) {
    for(var i = 0; i < 5; i++) {
        func(i);
    }
}

callFiveTimes(alert);  //Alerts numbers 0 through 4

Ответ 4

Передача функции в качестве параметра в другую функцию полезна в ряде ситуаций. Простейшей является функция типа setTimeout, которая принимает функцию и время и после этого времени выполнит эту функцию. Это полезно, если вы хотите что-то сделать позже. Очевидно, что если вы вызвали функцию и передали результат функции setTimeout, это было бы уже и не произойдет позже.

Еще одна приятная ситуация - это когда вы хотите выполнить какую-то настройку и отключение до и после выполнения некоторых блоков кода. Недавно у меня была ситуация, когда мне нужно было уничтожить аккордеон jQuery UI, сделать некоторые вещи, а затем воссоздать аккордеон. Мне нужно было сделать несколько разных форм, поэтому я написал функцию под названием doWithoutAccordion(stuffToDo). Я мог бы передать функцию, которая была выполнена между разрывом и настройкой аккордеона.

Ответ 5

Callbacks. Предположим, что вы делаете что-то асинхронное, например, вызов AJAX.

doSomeAjaxCall(callbackFunc);

И в doSomeAjaxCall() вы храните обратный вызов для переменной, например var ajaxCallback Затем, когда сервер возвращает свой результат, вы можете вызвать функцию обратного вызова для обработки результата:

ajaxCallback();

Ответ 6

Это, вероятно, не будет иметь большого практического применения для вас как веб-программиста, но есть еще один класс использования функций как объектов первого класса, которые еще не появились. В большинстве функциональных языков, таких как Scheme и Haskell, передача функций вокруг в качестве аргументов - это, наряду с рекурсией, мясо-картофеле программирования, а не что-то с редким использованием. Функции более высокого порядка (функции, которые работают на функциях), такие как карта и сложение, обеспечивают чрезвычайно мощные, выразительные и читаемые идиомы, которые не так легко доступны на императивных языках.

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

for(ball : ball_list) {
   ball.update();
   ball.display();       
}

Вместо этого я напишу (в Схеме)

(display (map update ball-list))

или в Python, который предлагает несколько функций более высокого порядка и более знакомый синтаксис,

display( map(update, ball-list) )

Fold принимает двухместную функцию, значение по умолчанию и список и применяет эту функцию к элементу по умолчанию и первому элементу, затем к результату этого и второго элемента и т.д., наконец, возвращает последнее возвращаемое значение. Поэтому, если мой сервер отправляет партии транзакций счета, вместо записи

for(transaction t : batch) {
    account_balance += t;
}

Я бы написал

(fold + (current-account-balance) batch))

Это просто простейшее использование наиболее распространенных HOF.

Ответ 7

Я проиллюстрирую это со сценарием сортировки.

Предположим, что у вас есть объект для представления Employee компании. Сотрудник имеет несколько атрибутов - id, возраст, зарплату, опыт работы и т.д.

Теперь вы хотите отсортировать список сотрудников - в одном случае по идентификатору сотрудника, в другом случае - по зарплате и в другом случае по возрасту.

Теперь единственное, что вы хотите изменить, - это сравнение.

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

Пример кода:

function compareByID(l, r) { return l.id - r.id; }
function compareByAge(l, r) { return l.age - r.age; }
function compareByEx(l, r) { return l.ex - r.ex; }

function sort(emps, cmpFn) {
   //loop over emps
   // assuming i and j are indices for comparision
    if(cmpFn(emps[i], emps[j]) < 0) { swap(emps, i, j); }
}