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)

Ответ на: комментарий от no-such-file

А. Ну тогда и первый случай не замыкание.

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

Если язык поддерживает замыкания, то не схлопывается.

але, смотри

функция А, вызвала B, В вернула замыкание на свою локальную переменную. ты говоришь - фрейм В не схлопнется… как тогда А возобновит работу, если фрейм B на стеке, и как она вызовет функцию С, если на стеке все еще фрейм B???

все застряло. начинайте новый тред, с новым стеком.

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

функция А, вызвала B, В вернула замыкание на свою локальную переменную. ты говоришь - фрейм В не схлопнется… как тогда А возобновит работу, если фрейм B на стеке, и как она вызовет функцию С, если на стеке все еще фрейм B???

А он не на стеке.

Вот пример для ушибленных си++:

B b = new B(); // вот инициализировали функцию b с её фреймом

int a()
{
  int x = b(); // вот выполнился код функции b, но замыкание сохранилось
  int y = c(); // вот выполнили другую функцию
}
monk ★★★★★
()

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

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

Вот пример для ушибленных си++:

B b = new B(); // вот инициализировали функцию b с её фреймом

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

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

ты сказал, что фрейм останется на стеке.

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

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

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

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

Если функция использует только глобальные переменные,

В JS есть модули - так что

let x = 5;

не является глобальной, она не доступна из других файлов

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

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

alysnix ★★★
()
Ответ на: комментарий от alysnix
def фрейм_который_захлопнулся():
    x = []
    def замкнутая_на_x_функция(y):
        x.append(y)
    def еще_одна_замкнутая_на_x_функция():
        return ' '.join(x)
    return замкнутая_на_x_функция, еще_одна_замкнутая_на_x_функция

def порождение():
    копи1, выдавай1 = фрейм_который_захлопнулся()
    копи2, выдавай2 = фрейм_который_захлопнулся()
    return копи1, выдавай1, копи2, выдавай2

def применение1():
    копи1, выдавай1, копи2, выдавай2 = порождение()
    копи1('да')
    копи2('что')
    копи2('такое')
    копи2('замыкание')
    копи1('почитай')
    копи1('ты')
    копи1('уже')
    return выдавай1, выдавай2

def применение2(выдавай1, выдавай2):
    print(выдавай1())
    print(выдавай2())

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

В твоем примере ничего подобного не видно.

Предполагается что эта функция (getX) будет заимпорчена в другой файл, я же написал export для демонстрации этого. Собственно в вызове ничего необычного не будет import getX ... console.log(getX())

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

Если переменная не внутри функции, то доступ к ней итак есть.

Это модюли в JS, функция getX экспортируется из этого файла в другой, где напрямую доступа к X нет

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

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

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

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

Не проверял, но произойдет именно это

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

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

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

если жаваскрипт делает так… то производительности от него не жди особо.

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

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

Обычно оно реализуется так:

компилятор видит переменные, которые потенциально «утекают» из функции.

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

Т.е. по факту копия. Ну или ссылка. Кому как больше нравится называть.

В языке, конечно, оно всё смотрится одинаково.

В Java особенно забавно. Там такой автоматики нет и в замыкание копируются значения переменных на момент создания этого самого замыкания. Поэтому если хочется иметь, например, изменяемое общее число для нескольких замыканий, то делают new int[]{0} и его уже используют. В общем примерно то же, что делают нормальные языки, только ручками.

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

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

Вот нормальный язык с замыканием:

function makeB()
{
    var x = 5;
    return function()
    {
       x++;
       return x;
    }
}

Вот аналог на си++

class B
{
  int x;
  B() { x = 5; }
  int operator() { x++; return x; }
}

Локальная переменная в си++ является полем объекта. В языках с нормальными замыканиями компилятор сам определяет, какое имя является константой, какое разместить на стеке, а какое в контексте замыкания.

ты сказал, что фрейм останется на стеке.

Я сказал, что фрейм функции, возвращающей замыкание не схлопывается. Может остаться и на стеке, если замыкание b размещено на стеке. Если же замыкание возвращается вовне, то размещено на стеке оно быть не может.

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

нет, просто не хотел оставить без ответа твое 4.2, а то вдруг поверит кто.

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

alysnix ★★★
()

Дружище, без обид, рано тебе ролики снимать с такой кашей в голове. Подумай о тех, кто это посмотрит.

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

В JS есть модули - так что

Модули это пространства имён. Переменные в них всё равно глобальные (по времени жизни видно).

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

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

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

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

Т.е. по факту копия. Ну или ссылка. Кому как больше нравится называть.

То есть черное. Или белое. Кому как больше нравится называть.

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

В Racket используется стек и замыкания на нём. Только там не аппаратный стек, а свой, который не освобождается после выполнения функции, а освобождается сборщиком мусора.

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

Да хоть в 16 байт, но расписанное понятнее же будет. JS: (x=>()=>x)(3)().

t184256 ★★★★★
()
Ответ на: комментарий от t184256
f = () => {
  var x = 3; return () => x;
}
a = f()();

это плохой пример. ни о чем.

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

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

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

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

рано тебе ролики снимать с такой кашей в голове.

В чем моя каша в голове?)

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

Модули это пространства имён. Переменные в них всё равно глобальные (по времени жизни видно).

Как это глобальные?

a.js
let x = 5;
b.js
import 'a.js'
console.log(x) // error, x is not defined
abs ★★★
() автор топика
Ответ на: комментарий от abs

а если код просто выдаст ошибку о том, что переменная уже была объявлена?
Допустим сначала в коде был импорт твоего модуля, а потом объявление переменной с таким именем, что и в модуле.

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

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

С чего вдруг? Я лет 5 пишу на современном (es6 с модулями) js, что за гадание на кофейной гуще?

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

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

Не пугай корм.

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

ну а объявление через var в модуле к чему приводит?

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

abs ★★★
() автор топика
Ответ на: комментарий от alysnix
f = () => { var x; return [() => x, y => x = y] };
[r, w] = f()
w(100)
r()  # 100

С учетом того, что в первом примере все это и даже больше было, а ты так и сподобился называть замыкания «замыканиями», а не «нашими замыканиями», четвертый и далее примеры по $10 штука.

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

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

Так приводили же уже пример: Неточности в определении замыкания в javascript (комментарий)

Но можно и так:

let f = () => {
  var x = 5
  return { "getX" : function()
    {
       return x;
    },
    "setX" : function(x1)
    {
        x = x1;
    }
  }
}

let s = f()
s.setX(100)
console.log(s.getX()) // возвращает 100
monk ★★★★★
()
Ответ на: комментарий от xmikex

С чего вдруг? Я лет 5 пишу на современном (es6 с модулями) js, что за гадание на кофейной гуще?

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

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

Не пугай корм.

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

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

Как это глобальные?

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

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

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

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

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

Но можно и так:

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

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

первый способ быстрей. и лучше.

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

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

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

ну а объявление через var в модуле к чему приводит?

К тому же самому. Переменная будет локальна в пределах одного модуля.

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

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

И что? Это просто переменная. Когда происходит процедуры ;) вызов, что делает JS?

Процедууууууры - SIcP даром не проходит…

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

Также как private секция в классах си++.

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

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

если жаваскрипт делает так… то производительности от него не жди особо.

Так нынче все современные языки делают так безотносительно замыканий. Что умные указатели в си++, что объекты в Java и C#. Стек - средство оптимизации компилятора.

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

Потому что ты эмулируешь тупость, а у него тупая хардварь. Оно, слышь, «пять лет пишет на современном жс».

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

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

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

let f
{
  let x = 1
  f = () => x
}

console.log(f()) // 1
console.log(x) // ReferenceError: x is not defined
javascript
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.