Лексические параметры видимости в javascript
Работая через Javascript Koans, я зацикливался на следующем коде:
it("should use lexical scoping to synthesise functions", function () {
function makeMysteryFunction(makerValue)
{
var newFunction = function doMysteriousThing(param)
{
return makerValue + param;
};
return newFunction;
}
var mysteryFunction3 = makeMysteryFunction(3);
var mysteryFunction5 = makeMysteryFunction(5);
expect(mysteryFunction3(10) + mysteryFunction5(5)).toBe(FILL_ME_IN);
});
Он получает 23, и это нормально. Я озадачен тем, как / почему параметр, переданный переменной 'mysteryFunction3', передается в функцию doMysteriousThing как 'param'.
Является ли это просто фактом жизни, что если есть внутренняя и внешняя функция, каждая из которых принимает один параметр, определяя переменную, равную внешней функции, заданной заданным параметром, например:
var mysteryFunction3 = makeMysterFunction(3);
сделает так, чтобы отправка параметра в экземпляр переменной внешней функции, например:
mysteryFunction3(10)
будет ли этот параметр (10) считываться как параметр для внутренней функции?
3 ответа
Поначалу я тоже пытался понять это. Вот как я достиг ясности с ударом за ударом. Это мой первый пост в стеке, и я прошу прощения за то, что я растянул его.
Возьмем очень простую функцию, возвращающую строку:
function fun() {
return 'Hi!';
}
Если мы регистрируем указанную выше функцию без скобок вызова, консоль просто регистрирует ссылку на функцию:
console.log(fun); //logs ---> [Function: fun]
Если мы зарегистрируем его снова, но с круглой скобкой вызова:
console.log(fun()); //logs ---> Hi!
вызов функции равен ее возвращаемому значению:
console.log(fun() === 'Hi!'); //logs ---> true
Исходя из этого, давайте перепишем нашу функцию, чтобы в ней была объявлена другая функция, возвращающая строку. Внешняя функция вернет вызов внутренней функции:
function funAgain() {
function innerFun() {
return 'Hello there!';
}
return innerFun();
}
Итак, в рамках funAgain (innerFun () === 'Hello there!') Оценивается как true, поэтому, когда мы регистрируем вызов funAgain в консоли:
console.log(funAgain()); //logs ---> 'Hello there!'
Но что, если мы не будем использовать вызывающие скобки для innerFun в операторе возврата внешней функции?
function funAgain() {
function innerFun() {
return 'Hello there!';
}
return innerFun;
}
console.log(funAgain()); //logs [Function: innerFun]
Возвращается функция САМ. Хотя на самом деле это еще не вся история, мы могли бы подумать о (funAgain () === innerFun). Очевидно, вы не можете фактически запустить это сравнение на практике из-за проблем с областью видимости (innerFun не может существовать вне вызова funAgain) . НО! Давайте на мгновение подумаем об этом так. Это означает, что если мы зафиксируем возвращаемое значение funAgain в переменной:
var innerFunCaptured = funAgain();
console.log(innerFunCaptured); // logs [Function: innerFun]
Мы снова концептуально имеем (innerFunCaptured === innerFun) ...
Итак, теперь, когда наша переменная привязана к внутренней функции, мы можем вызвать эту внутреннюю функцию, добавив к переменной круглые скобки.
console.log(innerFunCaptured()); //logs ---> 'Hello there!'
Когда я говорил о «всей истории» выше, я упустил то, что привязка внутренней функции к переменной является результатом вызова внешней функции, поэтому на самом деле привязка включает не только сам innerFun, но ТАКЖЕ и среду, которую она был создан внутри, включая любые потенциальные аргументы, переданные через вызов внешней функции, что позволяет нам ...
Перепишите внешнюю и внутреннюю функции еще раз, чтобы у них теперь были параметры, которые взаимодействуют:
function funOnceMore(greetingPartOne) {
function innerFun(greetingPartTwo) {
return greetingPartOne + ' ' + greetingPartTwo;
}
return innerFun;
}
Что, если мы зарегистрируем funOnceMore с аргументом.
console.log(funOnceMore('Hello')) //logs ---> [Function: innerFun]
Опять же, возвращается сам innerFun. Но как насчет аргумента HelloPartOne, который мы передали? Что ж, все прошло нормально, но поскольку innerFun никогда не вызывается в funOnceMore, приветствиеPartOne никогда не использовалось каким-либо значимым образом. Нам нужно выяснить, как вызвать innerFun! Ответ: нам нужно привязать его к переменной, как мы это делали на предыдущем шаге.
var innerFunCapturedAgain = funOnceMore('Hello')
Теперь innerFunCapturedAgain содержит innerFun И среду funOnceMore с аргументом Hello, который мы ему передали.
Итак, теперь мы можем вызвать innerFun, заключив круглые скобки в innerFunCapturedAgain, и эти круглые скобки будут инкапсулировать аргумент для welcomePartTwo, который мы передаем в innerFun.
console.log(innerFunCapturedAgain('there!')) //logs ---> 'Hello there!'
Оба ответа очень полезны, но, преодолев это самостоятельно, я думаю, что лучший ответ - просто пройтись по происходящему и изменить его настолько, чтобы пролить на него новый свет:
makeMysteryFunction
делает функцию, которая добавляет свой аргумент (makerValue
) к аргументу, который передается функции, которую он возвращает (mysteryFunctionX
).
Итак, что помогло мне понять это преобразование чисел в строки:
function makeGreeting(greeting)
{
var newFunction = function greet(name)
{
return greeting + ' ' + name;
};
return newFunction;
}
var makeGreetingHi = makeGreeting('Hi');
var makeGreetingHello = makeGreeting('Hello');
//finally call functions
makeGreetingHi('Stranger');
//Hi Stranger
makeGreetingHello('Friend');
//Hello Friend
Все, что я делал, это менял имя функций и объединял строки вместо добавления чисел. Что сбивает с толку коан, так это названия функций: думаю, это хороший пример плохой практики. Дело в том, что в приведенном мною примере greet
функция имеет доступ к greeting
, К сожалению, это потеряно в номенклатуре
Что-то, что я нашел очень полезным для понимания того, что именно здесь происходит, - это добавление console.log, чтобы показать содержимое "mysteryFunction3". Так:
var mysteryFunction3 = makeMysteryFunction(3);
var mysteryFunction5 = makeMysteryFunction(5);
console.log(mysteryFunction3);
expect(mysteryFunction3(10) + mysteryFunction5(5)).toBe(FILL_ME_IN);
Вывод консоли был таким:
doMysteriousThing(param)
{
return makerValue + param;
}
Исходя из C# фона, это безумие. Но, по крайней мере, теперь я это понимаю!