Функции объемного вызова внутри функций
Надеюсь, что кто-то найдет время, чтобы немного рассказать о функциях в функциях и области.
Я пытаюсь понять немного больше о функциях и объеме переменных и нашел неплохой учебник, но этой части я просто не получаю.
Задача:
Создайте функцию, которая будет работать следующим образом: 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
? Было бы здорово, если бы кто-нибудь дал некоторые объяснения.
Ответы
Ответ 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