LINUX.ORG.RU

Неточности в определении замыкания в javascript

 , ,


0

1

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

Определение из mdn (на английском тоже самое)

Замыкание — это комбинация функции и лексического окружения, в котором эта функция была определена. Другими словами, замыкание даёт вам доступ к Scope (en-US) внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз при создании функции, во время её создания.

Два фрагмента кода

const fn = () => {
  let x = 5;
  if (true) {
    console.log(x); // Вот тут x не существует в текущем (if-овском) скоупе, переменная ищется (lookup?) в родительском (fn-овском) 
  }
}

Вопрос, откуда берется значение переменной x? Хочется сказать и думать что из замыкания, но замыкание работает с функциями, тут же блочный скоуп

Или вот например если у нас есть файл

let x = 6;
export const getX = () => x

в данном случае переменная x не находится в скоупе внешней функции, она находится в скоупе модуля

[UPD] Так по факту и не разобрались, но большинство склоняется к тому что второй пример является замыканием, а значит на mdn была неточность.

★★★

Последнее исправление: abs (всего исправлений: 3)
Ответ на: комментарий от javascript

Внешняя область видимости может быть не только функциональной (не только от внешней функции).

вот я собственно про это и спрашиваю) Может ли мы называть это замыканием или нет

abs ★★★
() автор топика
Ответ на: комментарий от alysnix

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

t184256 ★★★★★
()
Ответ на: комментарий от abs

В JS общепринято что глобальная переменная та которая доступна из любого места в коде

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

P.S. Ладно, давай другой термин. Но если функция ссылается только на внешние переменные с вечным временем жизни, то она не замыкание. Суть замыкания в том, что время жизни связанных данных определяется временем жизни замыкания.

monk ★★★★★
()
Ответ на: комментарий от monk

Так нынче все современные языки делают так безотносительно замыканий.

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

но тот вариант с отцепом переменной в кучу, если она захвачена - работает вполне. более чем уверен что в жиэсе и ему подобных так и сделано.

alysnix ★★★
()

По существу.

  1. Замыкание - это прежде всего функция с привязанным к ней контекстом (скоупом), который по отношению к этой функции является внешним. Что видно и из определения на MDN, никакого противоречия в нем нет.

  2. Да, в js по факту абсолютно всё является замыканием, что ты можешь наблюдать в том же инспекторе - у любой функции есть как минимум один привязанный внейшний скоуп - глобальный. Но, ввиду того, что концепция замыкания используется во множестве языков, если функция использует только глобальный скоуп - ее не называют замыканием, а просто функцией.

  3. К слову говоря, в жс нет никаких функций - это все Callable Object’s, на секундочку.

  4. В твоих примерах в ОП-посте первый не демонстрирует никакого замыкания - «ифовский скоуп», который во-первых у тебя там и не создается, так как никаких локальных переменных в блоке ифа ты не объявляешь - не является замыканием - потому что он НЕ функция. Он часть функции сам по себе.

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

javascript
()
Ответ на: комментарий от javascript

Она так же может быть, например, блочной.

Это тоже не замыкание. В ссылке на f нет необходимости хранить ссылку на x, так как она однозначно определяется строкой кода.

monk ★★★★★
()
Ответ на: комментарий от abs

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

А у тебя просто доступ к области видимости модуля из функции модуля.

xmikex ★★★★
()
Ответ на: комментарий от javascript

По существу.

вот и сам жаваскрипт пришел. счас он нам расскажет, как устроен внутри, как снаружи, вывернет все карманы и выведет всех на чистую воду.

alysnix ★★★
()
Ответ на: комментарий от alysnix

но тот вариант с отцепом переменной в кучу, если она захвачена - работает вполне. более чем уверен что в жиэсе и ему подобных так и сделано.

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

monk ★★★★★
()
Ответ на: комментарий от javascript

К слову говоря, в жс нет никаких функций - это все Callable Object’s, на секундочку.

караул! это они наши функторы из плюсов сперли!

alysnix ★★★
()
Ответ на: комментарий от monk

Погоди, так тут что, надо его научить? @abs, ты действительно не отстреливаешь? Тогда сорян.

B0B
()
Ответ на: комментарий от monk

Это замыкание. И ссылка хранится. Скоупы в жс - это первоклассные объекты уровня спецификации. Каждая переменная - это просто свойство объекта.

Мой пример упрощен, но его можно усложнить

let a, b
{
  let x = 1
  a = n => x = n
  b = () => x
}

console.log(b()) // 1
a(2)
console.log(b()) // 2

javascript
()
Ответ на: комментарий от t184256

Да донесите на меня. Я же первый на ЛОР и начал коверкать, было дело в молодости ;) Теперь вот в правилах…

Я же пишу, что смузи на клавиатуру свою махонькую разлил, кнопки теперь не те жмутся.

Если кого обидел постом выше - прошу прощения, Господа. Никакого злого умысла не имел. (это всё увлечение напитками)

B0B
()
Последнее исправление: B0B (всего исправлений: 1)
Ответ на: комментарий от javascript

Замыкание - это прежде всего функция с привязанным к ней контекстом (скоупом)

Не привязанный, а относящийся именно к этому замыканию. При создании замыкания у каждого нового замыкания создаётся свой скоуп. Если он пуст, то это не замыкание, а простая функция. Разница та же, что между классом и модулем. У класса могут быть разные экземпляры, а у модуля нет.

monk ★★★★★
()
Ответ на: комментарий от javascript

Это замыкание. И ссылка хранится.

Сделай тогда второе замыкание с этими же a и b, но другим значением x.

У тебя сейчас в программе x может иметь только одно значение. А в замыкании в каждом экземпляре у x будет своё значение.

monk ★★★★★
()
Ответ на: комментарий от B0B

Если кого обидел постом выше - прошу прощения, Господа. Никакого злого умысла не имел.

А зря. Стоило бы.

anonymous
()
Ответ на: комментарий от monk

А в замыкании в каждом экземпляре у x будет своё значение

В каком каждом экземпляре? С чего ты взял, что это обязательное условие, что у замыкания должно быть много экземпляров? IIFE по-твоему не замыкания выходят? С чего вдруг? А если все же замыкания, то чем по-твоему мой пример отличается от IIFE?

let x = (x => n => x + n)(1) // это тоже замыкание, и у него лишь один экземпляр.

x(1) // 2
x(2) // 3
javascript
()
Последнее исправление: javascript (всего исправлений: 1)
Ответ на: комментарий от cdshines

Global Scope единственный на весь контекст исполнения. А Модулей в в контексте исполнения может быть много, и у каждого модуля будет отдельная область видимости, выше которой только глобальный скоуп.

javascript
()
Ответ на: комментарий от t184256

Я могу многого не знать, но тормозом себя не считаю. К чему повторять? Или ты хочешь, чтобы я удолил своё сообщение, которое было с таким трудом написано на липкой клавиатуре?

Я готов к бану. Сейчас в интернете писать нельзя, цензоры-с. На ЛОР многие ушли/умерли. Остался ты (тоже непростой товарищ, так скажем) да ещё пару регистрантов. Так что надо будет на «а вдруг» обратиться на ЛОР (за чем, не особо понятно) - создам новый акк. IP свой беленький сменю, et cetera.

B0B
()
Ответ на: комментарий от javascript

как в таком коде ты отличишь доступ к переменной прежней от создания новой переменной с тем же значением?

xmikex ★★★★
()
Ответ на: комментарий от B0B

Я хочу, чтобы ты почитал правила и прекратил недостойный якобы старожила лепет про дефект клавиатуры, якобы вынуждающий тебя менять местами буквы «а» и «о» в одном слове.

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

t184256 ★★★★★
()
Ответ на: комментарий от javascript

Перевожу: «а вдруг мы наблюдаем результат копирования значения, а не замыкания на, собственно, переменную! Меня не проведёшь! Потрудитесь привести буквально восьмой в этом треде пример опровергающий это блистательное изобличение!»

t184256 ★★★★★
()
Ответ на: комментарий от t184256

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

javascript
()
Ответ на: комментарий от javascript

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

xmikex ★★★★
()
Ответ на: комментарий от t184256

Да что же вы так душите то?

Сорян ТС, что оффтоп. Расскажите, почему? Жизнь стала тяжелая, или что происходит? Ты же моложе меня, а ведёшь себя… Мы все, все умираем, просто у кого-то уже есть бумажка с диагнозом, а некоторые в неведении. Так чего бы не повеселиться? Что за старческое брюзжание?

Я же не играю в контркультуру. Не пишу молодёжный сленг, чтобы замолодится. Как тут не взоржать, когда ТС такое пишет? Троллит - молодец. Не троллит - очень жаль это слышать. Особенно с опмсанием темы. Я про желание «учить».

Повторяю, зацепил, обидел? Прошу прощения, честно. Никого не хотел обидеть. Скучно живут погроммисты, эх.

B0B
()
Последнее исправление: B0B (всего исправлений: 1)
Ответ на: комментарий от B0B

Будет выводиться одно и то же значение счётчика или же будет расти оно. Если замыкание всего одно, то логично предположить возрастанию счётчика.

xmikex ★★★★
()
Ответ на: комментарий от xmikex

Я не понимаю, что ты несешь.

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

javascript
()
Ответ на: комментарий от javascript

В твоих примерах в ОП-посте первый не демонстрирует никакого замыкания - «ифовский скоуп», который во-первых у тебя там и не создается, так как никаких локальных переменных в блоке ифа ты не объявляешь - не является замыканием - потому что он НЕ функция

Проверил в хром дев тулз, и правда не создается, но это похоже на какую-то оптимизацию браузера, в документации сказано что «Обычно» создается

Usually an Environment Record is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement

abs ★★★
() автор топика
Ответ на: комментарий от xmikex

то логично предположить

Попробуй не предполагать. У тебя в твоем браузере есть целая доступна тебе консоль, открой, да проверь.

let x = (a => () => a++)(1)

x() // 1
x() // 2
x() // 3
x() // 4
javascript
()
Ответ на: комментарий от B0B

Не троллит - очень жаль это слышать. Особенно с опмсанием темы. Я про желание «учить».

Но у меня уже есть канал на ютубе, это лишь еще одно видео которое я хочу снять…

abs ★★★
() автор топика
Ответ на: комментарий от javascript

Замыкание - это прежде всего функция с привязанным к ней контекстом (скоупом), который по отношению к этой функции является внешним. Что видно и из определения на MDN, никакого противоречия в нем нет.

Вот часть из MDN, A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)

И тут действительно нет никакого противоречия, но теперь читаем дальше

In other words, a closure gives you access to an outer function’s scope from an inner function

И вот же оно, outer function’s scope это самое противоречие о котором я спрашиваю с самого начала

Ты вот говоришь

Будь его блоком любой блок, или же модуль - не суть важно.

Но на mdn написано a closure gives you access to an outer function’s scope - не просто доступ к внешнему скоупу, а именно внешнему скоупу функции.

abs ★★★
() автор топика
Последнее исправление: abs (всего исправлений: 1)
Ответ на: комментарий от abs

Но на mdn написано a closure gives you access to an outer function’s scope - не просто доступ к внешнему скоупу, а именно внешнему скоупу функции.

как жаваскрипт говорит, так оно и есть. а мсдн - не место, куда ходят с лора. стыд и позор!

alysnix ★★★
()
Ответ на: комментарий от abs

В браузерах множество оптимизаций.

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

Но относительно блочных скоупов, в которых не объявляется локальных переменных, их действительно ни к чему создавать в целом вообще.

javascript
()
Ответ на: комментарий от alysnix

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

так вот почему вебщина в нашей деревне такая медленная…

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от abs

Как я уже выше написал, блочный скоуп ничем не отличается от IIFE. Это первое.

Второе, ты слишком цепляешься к незначительному. Если так цепляться к словам, то на мдн в целом все написано неправильно, потому что в жс нет ФУНКЦИЙ вообще.

Третье - не стоит забывать, что MDN наполняется по принципу википедии, всеми кому не лень. Да и в самой википедии есть с десяток ссылок определений на некоторые вещи. Например, ты можешь однозначно ответить - что такое ТИП?

javascript
()
Ответ на: комментарий от javascript

Второе, ты слишком цепляешься к незначительному. Если так цепляться к словам, то на мдн в целом все написано неправильно, потому что в жс нет ФУНКЦИЙ вообще.

Третье - не стоит забывать, что MDN наполняется по принципу википедии, всеми кому не лень. Да и в самой википедии есть с десяток ссылок определений на некоторые вещи.

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

abs ★★★
() автор топика
Ответ на: комментарий от abs

В той же статье на mdn, в одном из примеров демонстрируется, как создаются замыкания на скоуп IIFE

The shared lexical environment is created in the body of an anonymous function, which is executed as soon as it has been defined (also known as an IIFE). The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. You can’t access either of these private members from outside the anonymous function. Instead, you can access them using the three public functions that are returned from the anonymous wrapper.

Those three public functions are closures that share the same lexical environment. Thanks to JavaScript’s lexical scoping, they each have access to the privateCounter variable and the changeBy function.

Посему, если ты хочешь избавиться от противоречий, то просто воспринимай module\block scope как частный случай IIFE. Они по существу ничем он IIFE и не отличаются, и раньше именно с помощью IIFE и эмулировались.

javascript
()
Последнее исправление: javascript (всего исправлений: 1)
Ответ на: комментарий от javascript

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

xmikex ★★★★
()
Последнее исправление: xmikex (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.