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)
Ответ на: комментарий от abs

Замыкание предоставляет функции

Замыкание это и есть функция. Буквально.

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

Речь разумеется про языки, где функции - это сущности первого класса.

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

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

Хоть мильён функций могут ссылаться на одну и ту же переменную. Если эта переменная внешняя по отношению к этим функциям - все они являются замыканиями.

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

Просто покажите ссылку https://learn.javascript.ru/closure Замыкание

ТС, не стесняйтесь создавать новые треды.
Треды, подобные этому

Полезны ...
anonymous
()
Ответ на: комментарий от monk

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

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

смысл замыкания(и не надо там читать всякие руководства) в том, чтобы дать хук, со всем контекстом, необходимым для его вызова, причем дать автоматически, и погромисту не надо ни о чем думать.

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

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

если переменная крадется одним замыканием - это просто на лету создаваемый функтор. но если переменаая шарится между двумя замыканиями - это одна копия в куче и два «функтора» ссылающихся на нее.

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

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

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

Пруфы будут? Потому как это противоречит документации ecmaScript

Никакой копии создаваться не будет, она просто изначально не будет удаляться пока на нее есть ссылки (А в замыкании как раз и будет ссылка)

abs ★★★
() автор топика

ТС… тебе пока не стоит снимать кино про замыкания пока ты не поймешь, что это на самом деле. даю строгое определение.

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

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

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

Никакой копии создаваться не будет, она просто изначально не будет удаляться пока на нее есть ссылки (А в замыкании как раз и будет ссылка)

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

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

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

Тема решенная, тут уже >200 комментариев какого-то бреда и оффтопика, а вся проблема оказалась в том, что определение MDN устарело и не учитывает новые понятия про module и block scope.

Так что разберитесь сначала с сутью моего вопроса (а она предельно проста, противоречия в определении MDN, а не в понимании замыкания)

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

что должны были по логике уже погибнуть.

Еще раз, наверное раз 30 говорю, это противоречит стандарту ecmaScript, ничего про «смерть» переменных (И снова в 20 раз говорю, замыкаются не переменные, а области видимости) там не написано. Прочитай про Environment Records, и перестань гадать на кофейной гуще

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

Ты понимаешь все правильно. Ничего не копируется.

Захватываются скоупы, со всеми используемыми в замыкании переменными.

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

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

Никакой копии создаваться не будет, она просто изначально не будет удаляться пока на нее есть ссылки

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

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

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

так что запруфь.

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

Захватываются скоупы,

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

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

так что запруфь.

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

Environment Record is a specification type used to define the association of Identifiers to specific variables and functions, based upon the lexical nesting structure of ECMAScript code. Usually an Environment Record is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement. Each time such code is evaluated, a new Environment Record is created to record the identifier bindings that are created by that code.

Every Environment Record has an [[OuterEnv]] field, which is either null or a reference to an outer Environment Record. This is used to model the logical nesting of Environment Record values. The outer reference of an (inner) Environment Record is a reference to the Environment Record that logically surrounds the inner Environment Record. An outer Environment Record may, of course, have its own outer Environment Record. An Environment Record may serve as the outer environment for multiple inner Environment Records. For example, if a FunctionDeclaration contains two nested FunctionDeclarations then the Environment Records of each of the nested functions will have as their outer Environment Record the Environment Record of the current evaluation of the surrounding function.

Environment Records are purely specification mechanisms and need not correspond to any specific artefact of an ECMAScript implementation. It is impossible for an ECMAScript program to directly access or manipulate such values.

Что мы тут видим,

Each time such code is evaluated, a new Environment Record is created to record the identifier bindings that are created by that code -

каждый раз когда исполняется код - создается record с текущими переменными, можно воспринимать это как scope,

Every Environment Record has an [[OuterEnv]] field, which is either null or a reference to an outer Environment Record.

Это плюс текст выше = каждый раз когда исполняется часть кода которая создает новый скоуп - этот код будет захватывать скоуп выше.

Про смерть в этом разделе ничего не написано

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

Там написано со всеми используемыми в замыкании переменными.

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

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

или только скраденные переменные?

Это ужас, я уже отвык от ЛОРа.

никакие переменные не «воруются». в замыкании хранится ссылка на родительский «скоуп» (ER) который из себя представляет некую словареподобную структуру (документация не оговаривает это и оставляет на усмотрения создателей JS движков)

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

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

вот. тут написано, что хватается весь envrecord, что и есть динамический скоп, русскими словами. то есть если он 10мб, то все они и будут в памяти, пока на них есть ссылка.

особо радует, что это надо, чтобы кнопы оживлять. круто, че.

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

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

Да, и что тебя в этом смущает? Если у тебя 10мб в скоупе ты явно что-то делаешь не так.

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

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

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

Это по спецификации. А по факту не зачем хранить ссылки на то, что никем не используется.

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

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

Если ты не можешь объяснить суть простой концепции человеку, который написал компилятор Оберона, как ты осмеливаешься объяснять что-то людям, которые вообще ничего не понимают?

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

GC автоматически почистит память

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

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

Если ты не можешь объяснить суть простой концепции человеку, который написал компилятор Оберона, как ты осмеливаешься объяснять что-то людям, которые вообще ничего не понимают?

Моя задача рассказать, их задача ХОТЕТЬ понять. Человек выше не осилил самостоятельно погуглить документацию после того как я неоднократно это упомянул. Но с уверенным видом спорил. Это за гранью моего понимания

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

чистит процессор

Да. Вилкой.

Осеннее обострение на ЛОРе.

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

Если ты не можешь объяснить суть простой концепции человеку,

я давно все понял жеж. вопрос был только в том как они это реализуют. ну в принципе, с этими env рекордами все будет работать, но пороком будет то, что можно всю память забить малонужным мусором.

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

то обе функции захватят один и тот же скоуп, но в нем будут сохранены только две используемые переменные. А все остальное соберет сборщик мусора.

пруф давай. в спецификации не так, сам же говоришь.

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

15 минут как?)

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

пока ты там блуждал в потемках.

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

раньше. когда мы еще вчера экспериментировали с двумя долгоживущими кложами

Так зачем ты продолжаешь бредить про воровство переменных? Раздвоение личности? Одна «все поняла» вторая только собирается «все понять»?

в спецификации не так, сам же говоришь.

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

А о том что в скоупе ДОЛЖНЫ хранится все переменные там ничего не сказано, это на усмотрение создателей движков, но в любом случае это лишь оптимизация которая не меняет суть

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

Это за гранью моего понимания

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

вот и все. вот договоритесь между собой…я то вообще js не использую.

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

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

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

Я этого не говорил.

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

Он все правильно говорит, и я тебе говорю о том же самом, но ты читаешь жопой на протяжении всего треда.

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

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

Чел, я атеист, но тут нужна помощь богов, тут есть на ЛОРе тор или один?

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

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

джаваскриптер говорит (и это правда) что движки могут оптимизировать скоуп и удалять из него лишнее переменные которые не используются замыканиями

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

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

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

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

ER это record (внезапно?), как именно он реализован внутри не оговаривается, но в документации написаны определенные требовования операций которые могут происходит с record из которых мы понимаем что он динамический.

Погуглил я нашел что время доступа к переменной это O(1) - так что там скорее всего что-то типа hash table

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

Погуглил я нашел что время доступа к переменной это O(1) - так что там скорее всего что-то типа hash table

это массив - 0(1). у «хеш таблицы» такая сложность, если она больше чем число элементов и коллизий нет. если они говорят, что гарантированно 0(1), то это массив.

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

а если это массив… то удалять из него «лишние переменные» - совсем не O(1).

ну и как устроен по вашему скоп?

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

for(let i = 0; i < 5; i++) f.push(n => n + i)

Да, тут вложенные функции.

А если сделать

for(var i = 0; i < 5; i++) f.push(n => n + i)

f[0](1) // 6

то уже никаких замыканий не будет (если for не внутри функции и f не вернуть наружу)

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

то уже никаких замыканий не будет (если for не внутри функции и f не вернуть наружу)

говорили балакали сіли та й заплакали

будет замыкание.

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

это массив - 0(1).

Ты уж определись, массив, связный список, или хеш.

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

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

будет замыкание.

И что в нём будет, если адреc i фиксированный и для запуска f[k] никаких дополнительных данных кроме адреса функции не требуется?

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

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

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

Ты уж определись, массив, связный список, или хеш.

вроде это ты определяешься :)

это ты определись, прежде чем кино снимать, как это реализовано внутри.

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

то есть придет клиент смотреть твое кино, задаст конкретный вопрос, а ты ему скажешь - не знаю как оно внутри работает, но там все волшебно.

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

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

Внутри это описание стандарта ER, можно упомянуть что время доступа O(1) - а как оно там реализовано в конкретном google chrome или firefox не имеет отношения к JS напрямую с одной стороны, и при этом использует ну очень сложные оптимизации на с++ (в случае V8) говорить о которых в JS смысла нет.

вроде это ты определяешься :)

Я не определяюсь, это ты начал городить чушь про

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

На что я тебе возразил что время доступа O(1) а значит никакого списка там нет. Что там на самом деле я нагуглить за пару минут не смог - хочешь узнать? гугли

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

вангую, что скоп там устроен так.

если в скопе 100 переменных, то это массив из 100 адресов этих переменных. и чтобы нормально «чистить ненужное», они аллокированы на куче. доступ к переменной идет по ее индексу в массиве. тогда O(1).

при закрытии скопа, деалокируются все незахваченные переменные. если удалилось все, и нет захваченных - сам массив, он же енврекорд, удаляется… если захваченные есть - весь массив остается в памяти под названием envrecord. там на удаленные элементы стоят нулы, на неудаленные - реальные адреса на куче

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

Будет. Просто все пять функций у тебя захватят один и тот же скоуп.

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

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

На что я тебе возразил что время доступа O(1) а значит никакого списка там нет.

ты не понял, что список был «контпримером» что-ли?

ты не с украины часом?

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

Да не статический он! Сколько можно? Смотри скриншот, там же ясно показаны все СКОУПЫ которые захвачены функцией.

Измени свою функцию так, что она присваивает значение переменной i. Добавь вторую функцию, третью, которые тоже работают с этой переменной.

Тебе сколько раз еще написать, что в js - все скоупы это ПЕРВОКЛАССНЫЕ ОБЪЕКТЫ, обычные, такие же как и все остальные объекты. Которые сложены в цепочку почти такую же как прототипная у всех остальных. При лукапе переменной она ищется в локальном скоупе, потом в выше стоящем, потом еще на уровень выше, и так до глобального скоупа - который в свою очередь является простым js объектом, и у него есть прототипы, поэтому при лукапе переменной в глобальном скоупе, лукап пойдет прямиком до null, через Object.prototype.

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

ты не понял, что список был «контпримером» что-ли?

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

Ты уж определись, могут они удаляться если там не список, или не могут. Потому что пока что

а) они могут удаляться (это можно проверить, скажем, в chrome dev tools)
б) время доступа O(1) пока что я принимаю это на веру, но это тоже можно проверить и углубится в поиски

А следовательно если объединить а и б - получается ты опять несешь бред, а потом еще и оправдываешься каким-то контрпримерами.

abs ★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.