Подтвердить что ты не робот

Функции объемного вызова внутри функций

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

Задача:

Создайте функцию, которая будет работать следующим образом: sum(a)(b) = a+b и принимает любое количество скобок. Примеры:

sum(1)(2) == 3
sum(5)(-1)(2) == 6

Решение:

function sum(a) {

    var sum = a;

    function f(b){
        sum += b;
        return f;
    }

    f.toString = function() { return sum };

    return f;         //line 12
}

alert( sum(1)(2) );   // 3e

Объяснение:

Чтобы сделать sum(1) вызываемым как sum(1)(2), он должен вернуть функцию. Функция может быть вызвана или преобразована в число с помощью valueOf. Решение действительно самоочевидно:

Моя интерпретация:

Этот f в function f(b) возвращается в область видимости, начиная с строки 02-12. f в f.toString, это текущий f из function(b) Следующий return f возвращается к области, которая находится вне функции sum(a).

Проблема:

Я не могу понять, где мне нужно думать по-другому, потому что, как я описал выше, функция не будет вызываться снова, поэтому где же часть кода, которая делает возможным "несколько круглых скобок"?

Кроме того, правильно ли я предположил, где возвращаются f? Было бы здорово, если бы кто-нибудь дал некоторые объяснения.

4b9b3361

Ответ 1

Функция sum возвращает функцию, которую мы называем f.

Функция f также возвращает функцию: на самом деле функция f возвращает себя.

Когда функция f определена внутри sum, она получает постоянный доступ ко всем переменным, видимым в настоящее время в цепочке областей видимости. Здесь он включает локально определенную переменную sum (локальная текущая сумма) и f (сама функция). ( "Закрытие" - это то, что мы называем функциональным кодом f вместе со всеми его переменными в области видимости.)

Поскольку f возвращает себя, вы можете цепью f с повторными вызовами:

var this_is_f = sum(1);
var same_f_again = this_is_f(2);
var f_a_third_time = same_f_again(3);

Или просто:

sum(1)(2)(3);

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

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

var first_tally = sum(1)(2);   // first: 3
var second tally = sum(4)(5);  // second: 9
first_tally(3);   // first: 6
second_tally(6);  // second: 15

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

Ответ 2

Если вы возьмете этот код и упростите его до минимума, мне будет легче понять. Возьмите функцию add, которая суммирует только 2 числа:

function add(x,y) {
  return x + y;
}

Вышеупомянутая функция является "нормальной". Если вы не пройдете все аргументы, вы получите неожиданные результаты.

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

function add2(x) {
  return add(2, x);
}

Но в JavaScript у нас есть функции первого класса (объекты, которые могут быть переданы), поэтому функция может принимать функции как входы и возвращать другие функции. Это то, где "каррирование" пригодится. Хотя "частичное приложение" позволяет исправить аргументы функции, "currying" принимает функцию множества аргументов и разбивает ее на функцию одного аргумента, которая возвращает другую функцию одного единственного аргумента, пока все аргументы не будут оценены, и затем возвращает результат. Например:

function add(x) {
  return function(y) {
    return x + y;
  }
}

Теперь вы можете создать функцию add2, выполнив функцию add:

var add2 = add(2);
add2(1); //=> 3

Нормальная функция и кардинальная имеют эквивалентные вычисления, где:

add(1, 2) === add(1)(2)

Это то, что делает возможным "несколько круглых скобок".

В JavaScript "область" и "закрытие" относятся к функциям, но они разные понятия. "Область" определяет охват/конфиденциальность ваших переменных, а "закрытие" позволяет инкапсулировать код и переносить его. В функции curried над переменной x хранится в памяти через закрытие, потому что она ссылается внутри возвращаемого функционального объекта.

Особое ограничение "currying" заключается в том, что вы не можете иметь функции динамической реальности; количество аргументов должно быть исправлено. Это не так в коде, который вы публикуете, где ваша функция sum должна иметь возможность добавлять новый номер к предыдущей сумме на неопределенный срок.

Аналогично "currying" возникает идея "генераторов"; шаблон для моделирования последовательностей ленивых вычислений, в этом случае просто добавляя число к предыдущей сумме по требованию.

function sum(a) { // begin closure
  var sum = a; // kept in memory...
  function f(b) {
    sum += b; //...because you use it here
    return f;
  }
  f.toString = function() { return sum };
  return f;
} // end closure

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

function add(a) {
  var sum = a;
  return {
    plus: function(b) {
      sum += b;
      return this;
    },
    sum: function() {
      return sum;
    }
  }
}

add(1).plus(2).plus(3).sum(); //=> 6

В вашем коде возвращаемая функция f действует как plus и toString как sum, которая извлекает значение.

Ответ 3

Chaining

Обратите внимание:

  • 4 вхождения f относятся к одной и той же функции.
  • Первая пара скобок вызывает sum, а остальные вызовы f.
  • sum возвращает f и f возвращает себя, другими словами, всякий раз, когда вы пишете (), вы возвращаете ту же функцию f.

Вкратце:

sum(0)       // sum(0) -> f
sum(0)(1)    // sum(0), f(1) -> f
sum(0)(1)(2) // sum(0), f(1), f(2) -> f

Постоянство

Таким образом, главный вопрос заключается в следующем: как состояние суммы может сохраняться вдоль этих вызовов функций? Именно здесь происходит эта знаменитая "закрывающая" вещь. Я не буду подробно обсуждать эту концепцию, Google может помочь гораздо лучше. В нескольких словах речь идет о соотношении между f и переменной sum. Действительно, с момента их появления, благодаря первому вызову функции, они будут использовать один и тот же частный контекст для остальной части их существования. Следовательно, каждый последующий вызов состоит в изменении существующей переменной sum на f:

sum(0)       // var sum = 0
sum(0)(1)    // var sum = 0, sum += 1
sum(0)(1)(2) // var sum = 0, sum += 1, sum += 2

Результат

Наконец, toString вызывается автоматически, когда вместо f ожидается строка:

typeof(sum(1)(1))      // typeof(f) -> "function"
typeof(sum(1)(1) + '') // typeof(f + '') -> typeof('2' + '') -> "string"
                                                    ^
                                                    toString was called

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

Bonus

Чтобы это, надеюсь, это объяснение было достаточно ясным. В качестве вывода, здесь аналогичный подход (чтобы получить конечный результат, просто положите пустую пару скобок в конце очереди):

function sum(a) {
    return function (b) {
        return arguments.length ? sum(a+b) : a;
    };
};

sum(1)(1)(1)(); // 3

Ответ 4

  • Строка 01-13 определяет функцию sum в пределах глобальной области.
  • Строка 15 вызывает sum и изменяет область внутри функции.
    • Переменные a и sum и функция закрытия f определены в области функций - с переменной sum, скрывающей определение функции из глобальной области.
    • Я пропущу бит toString здесь, так как это не важно дольше (когда это становится очень важным).
    • Функция sum, наконец, возвращает ссылку на функцию закрытия f на глобальную область как анонимную функцию (поскольку f может ссылаться только на имя изнутри своей области содержимого).
  • Вернуться к строке 15 - sum(1)(2) совпадает с (sum(1))(2), а так как sum(1) возвращает f, то это можно свести к (f)(2) или, проще говоря, f(2).
    • Вызов f(2) добавляет 2 в sum - sum определяется в двух областях: как функция в глобальной области; и в качестве переменной (в настоящее время назначается значение 1) внутри этой функции, которая скрывает определение из глобальной области действия - поэтому в f для переменной sum установлено значение 1+2=3.
    • Наконец, в f функция возвращает себя.
  • Вернуться к строке 15 - f(2) возвращает f (хотя именованную функцию f нельзя ссылаться в глобальной области действия и, опять же, обрабатывается как анонимная функция)
  • alert() обрабатывается, и анонимная функция (называемая f) преобразуется в строку, подлежащую предупреждению; обычно, когда alert() вызывается в функции, он будет отображать источник для функции (попробуйте прокомментировать строку 10, чтобы увидеть это). Однако, поскольку f имеет метод toString (строка 10), тогда он вызывается, и возвращается значение sum (как определено в функции, содержащей f), и 3 предупреждается.

Ответ 5

Существуют 3 концепции

  • закрытия (без области действия).
  • функции OBJECTS первого класса в javascript (позволяет связывать f(a)(b)(c)). Нет необходимости сохранять дескриптор функции для вызова позже.

Я расскажу вам об отказе, а затем добавлю дополнительные пояснения

function sum(a) {
  // when sum is called, sumHolder is created
  var sumHolder = a;
  // the function f is created and holds sumHolder (a closure on the parent environment)
  function f(b) {
    // do the addition
    sumHolder += b;
    // return a FUNCTION
    return f;
  }
  // change the functions default toString method (another closure)
  f.toString = function() {return sumHolder;}
  // return a FUNCTION
  return f
}
/*
 * ok let explain this piece by piece
 * 
 * you call sum with a parameter
 *  - parameter is saved into sumHolder
 *  - a function is returned
 * 
 * you call the returned function f with another parameter
 * EXPLANATION: { sum(a) } returns f, so let call f as this => {...}()
 *  - this private (priviledged) function adds whatever it been passed
 *  - returns itself for re-execution, like a chain
 * 
 * when it all ends {{{{}(a)}(b)}(c)} the remainder is a FUNCTION OBJECT
 * this remainder is special in that it toString() method has been changed
 *  so we can attempt to cast (juggle) it to string for (loose) comparison
 * 
 */

Концепцию закрытий довольно легко понять, но приложение заставляет вашу голову вращаться, пока вы не привыкнете к мысли, что в javascript нет области действия, есть закрытия strong > , и это мощные твари действительно

// this anonymous function has access to the global environment and window object
(function()
  {// start this context
    var manyVars, anotherFunc;
    function someFunc() {
      // has access to manyVars and anotherFunc
      // creates its own context
    };
    anotherFunc = function () {
      // has access to the same ENVIRONMENT
      // creates its own context
    };
  }// whatever is in these keys is context that is not destroyed and
  //  will exist within other functions declared inside
  //  those functions have closure on their parent environment
  //  and each one generates a new context
)();

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

// how about calling an anonymous function just as its created
// *cant do the next line due to a language constraint
// function(){}()
// how about a set of parens, this way the word "function" is not the first expression
(function(){}());
// the function was created, called and forgotten
// but the closure inside MAY STILL EXIST
function whatDoIReturn() {
  return function (){alert('this is legal');return 'somevalue';}();// and executed
}// returns 'somevalue'

Не принимайте это слово в слово. Пойдите, ищите код других людей, проверьте Crockford и задайте все вопросы, которые возникают

Ответ 6

Перейдите через функцию по строкам:

function sum(a) {

Эта часть довольно понятна; мы имеем функцию с именем sum, которая принимает аргумент a.

    var sum = a

Здесь у нас есть локальная переменная с именем sum, которая задана значением передаваемого аргумента.

    function f(b) {
        sum += b
        return f
    }

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

    f.toString = function() { return sum }

Это интересная часть! Когда вы обычно console.log используете функцию, она просто выплюнет источник функции. Здесь мы переопределяем метод toString вместо того, чтобы быть функцией, которая выплевывает значение sum. Вот почему вы в конечном итоге видите текущее общее количество, которое отображается вместо источника функции, даже если возвращаемое вами значение по-прежнему является функцией.

Наконец, мы имеем:

    return f

Итак, у вас есть функция sum, возвращающая функцию, которая возвращает себя. Функция sum в основном устанавливает все, но после вызова sum(3) вы затем работаете с f, который вы повторно вызываете.

Итак, в первый раз, когда вы вызываете sum, вы, по сути, возвращаете эту функцию:

function f(b) {
   sum += b; //value of sum is 3
   return f;
}

В этом контексте значение sum будет значением a, которое вы передали в начальный вызов функции sum. Однако, поскольку параметр toString of f был переопределен, вы увидите только значение 3. Тогда скажите, что вы делаете sum(3)(4).

Вы возвращаетесь, как и раньше:

function f(b) {
   sum += b; //value of sum is 3
   return f;
}

Но тогда вы на самом деле вызываете f с аргументом 4 (в основном f(4)). Поскольку f является внутренней функцией, он имеет полный доступ к области своей родительской функции. Эта родительская функция (sum) поддерживает текущее значение в переменной с именем sum, доступной для f. Поэтому, когда вы вызываете f(4), у вас b установлено значение 4 и sum со значением 3:

function f(b) { //b is 4
   sum += b; //value of sum is 3 + 4, which is 7
   return f;
}

Таким образом, с каждой следующей круглой скобкой вы делаете повторные вызовы с тем же f, который поддерживает текущий подсчет.

Другой способ - подумать о sum как о factory, который может дать вам разные f, все из которых сохраняют свои собственные бегущие таблицы (в основном ведут себя как аккумуляторы):

var firstSum = sum(4);
var secondSum = sum(2);

firstSum(5); //equivalent to sum(4)(5) returns 9
secondSum(2); //equivalent to sum(2)(2) returns 4