Розбий моноліт! Kuberton — конференція для DevOps-ів & Python, Java, Ruby, GO розробників. 2-3 March, 2019
×Закрыть

JS вопрос по замыканию

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

function solution(num) {
     
    function sum(num) {
        let factorial = num;
        for(let i = 1; i < num; i++) {
            factorial *= i;
        }
        return factorial;
    }
    let sumResult = sum(num); 
    
    let cash = [];
    
    function casher() {
        cash.push(num);
        return cash;
    };
    
    let func1 = casher();
    
    return func1;
}

на данном этапе я решил просто протестить что в кэше. Кэш не сохронят свой предыдущий state при новом вызове функции((( В sources смотрел что на этапе выполнения casher() массив cash был в closure.

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
const factorial= (()=>{
        const cache= [];
        return n=> n ? cache[n] || (cache[n] = n * factorial(n - 1)) : 1;
    })();

Самый нечитаемый вариант. Прикинь во сколько вылетает ошибка в подобном коде. И как мать его это тестить? Особенно если там не будет явного вылива всех данных.

Самый нечитаемый вариант

Да как бы напротив- для js весьма традиционный код с незапамятных времен, кратко и читаємо, что ошибку тут уж трудно допустить.

И как мать его это тестить?

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

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

jsfiddle.net/qn4yersz

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

UPD: поместил код в FiddleJS а то на DOU не понимаю как многострочный код вставить

как многострочный код вставить

через тег pre

Спасибо

const funcWithState = (() => {
    const state = []

    return n => {
        state.push(n)
        return state
    } 
})()

console.log(funcWithState(1)) // [1]
console.log(funcWithState(2)) // [1, 2]
console.log(funcWithState(3)) // [1, 2, 3]

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

При чем тут сахар? Это базовый метод создания приватных переменных в функциональном подходе и он был доступен еще с первых версий EcmaScript

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

В Java например так память не сольёшь. Назвался функцией высшего порядка — сиди в своём анонимном классе и не высовывайся, работать будешь когда ссылки дадут, и забудешь их когда отработаешь. Хочешь большего — пиши свой класс, с блэк-джеком и плюшками.

Как человек который писал раньше на Java понимаю ваше негодование. Но будьте уверены — неявным оно кажется только для тех кто пишет на языках без функций. С точки зрения JS разработчика тут все очень явно и очевидно

То-то оно всё течёт не только лишь везде.

Вы там на джаве беспокоитесь осознает ли кодер, используя ваш класс, как там организована его приватная начинка? :) Замыканиями пронизан весь js, без него делать тут нечего.

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

И чем это функционально отличается от реализации js? Функциям в js дана особая гибкость, никаких классов, все же отлично.

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

Ведь она замкнётся даже если там нет алгоритма кеширования, не так ли? Вот и представь, если эта хрень генерится динамически под каждый чих, сколько такого счастья. А отлаживать его песец как сложно.

Функциям в js дана особая гибкость

Даже я так высоколитературно не мог бы сказать «говно».
Особо радуют писатели плагинов для чужих сайтов, которые переопределяют объект console. ИЧСХ, браузеры это пропускают!

Не ждет того что функция «замкнется» в момент создания только тот кто не вообще не владеет языком, по тому что замыканием обладают поямо с момента создания всегда ВСЕ функции

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

Ну а про «говно» дак это вообще похоже на попытку доказать самому себе что Java самый лучший язык программирования по тому что вы на нем пишете. Из всего JS выбрать для нападения что-то что в нем действительно сделанно лучше чем в Java это лол :)

Нихера. Типовая JS-функция не несёт в себе замыкание объекта её создавшего — она без роду без племени, и ей this прилетает во время вызова.

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

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

Ок, давай тогда пойдем с определений. Википедия говорит


Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.

В данном случае привожу пример на русском по тому что на английском определение такое-же

То есть замыкание это функция + переменные скоупа в котором она объявленна. Скоуп объявления всегда есть, он всегда есть в момент создания создания, и он не может менятся (в случае JS)

this, с другой стороны, это динамическая переменная. Она ссылается на объект на которм функция вызвана как метод (для => правило другое, но суть та-же). Если функция вызвана как функция то this вообще не определен (раньше, до strict mode, было не так, но это детали). Это значит если у нас есть функция f и мы делает f() то this будет undefined. Если мы делает const a = {}; a.f = f; a.f() то this будет a. Если мы после этого решили const b = {}; b.f = a.f; b.f() то this станет b

Веду я к тому что this с замыканием вообще никак не пересекается

Ну и из всего этого следует что концепция используемоя для функций в JS это не свежая придумка, и не одна из множества ошибок допущенных Бернардом Эйчем когда он за 2 недели спасал мир от Visual Basic. Этой концепции уже 50+ лет за которае она успела показать себя со всех сторон и будет присутствовать в любом языке где функции first-class citizen

Ничего плохого в замыканиях нет. Плохое есть в том КОГДА они создаются.

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

Я считаю так: анонимными должны быть сущности с очень коротким временем жизни, или же с точно определённой принадлежностью к именованной сущности и без неё не управляемые.

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

Еще раз определение: замыкание это функция + скоуп в котором она определена. И функция, и скоуп, и больше ничего. Тут нет еще какого-то объекта который может быть забыт сам по себе. Скоуп это и есть объект. В случае с JS это особый объект на который нельзя получить ссылку и у котогого все поля не перезаписываемы (до strict mode были исключения, но снова же, они тут не к месту)

Фразу функция которая не имеет замыкания скорее можно интерпритировать как функция которая не имеет доступа к скоупу в котором она объявлена. В создании такого ограничения смысла столько же сколько в добавлении в Java методов которые не могут использовать this

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

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

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

А когда ты пишешь казалось бы независимую функцию

В смысле вызываете, а не пишите?

как-то не ждёшь что она замнкнётся уже в момент создания.

А вам зачем ждать или не ждать этого? Это приватная ее реализация, как она там функционирует это не ваша забота, как и не ваша забота как функционирует кем то написанный класс изнутри, вам же дали public который нужно дергать :)

Ведь она замкнётся даже если там нет алгоритма кеширования, не так ли?

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

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

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

А отлаживать его песец как сложно.

Практически ничем не отличается.

При том, что замыкания создаются неявно, кодер их не видит и даже не подозревает об их существовании

Почему же это? Я замыкания создаю вполне намеренно и хорошо их вижу в коде.

В том-то и дело, что нет.
Намеренно — это когда ты присваиваешь переменную, с пониманием что она может тянуть за собою контекст создания. Но только в случае если этот объект таковым создан. Если ты присвоишь, к примеру, String, он за собой всю цепочку родителей в памяти не оставит.

В случае же синтаксического сахара типа n => {return n+1} ты не видишь, что прицепил сюда контекст (то есть замыкание) текущей области кода, там где ты его создал. Ты создал и забыл, передал куда-то и думаешь что сделал дело. В реальности же в памяти зависла вся цепочка объектов. И горе тебе, если это была рекурсия. Особенно бесконечная (в JS это допустимо).

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

В случае же синтаксического сахара типа n => {return n+1} ты не видишь, что прицепил сюда контекст (то есть замыкание) текущей области кода, там где ты его создал.

Хочешь сказать что я не знаю как работает arrow function? 😀

В реальности же в памяти зависла вся цепочка объектов.

В вот этом вот случае, до которого ты докопался

const funcWithState = (() => {
    const state = []

    return n => {
        state.push(n)
        return state
    } 
})()

В памяти зависнет что? Подсказываю, this в таком случае — ссылка на window, который и так никуда из памяти не денется. Если только ты не будешь делать такого в методах объектов, которые за время жизни приложения много раз создаются и уничтожаются, то даже если что-то будет зависать в памяти, это совершенно не критично.

Откуда стрелочная функция знает, что надо тащить state за собой? Ответ: ниоткуда. Тащится не state, тащится объект его содержащий. И если бы statе не было или не вызывалось, этот объект всё равно тащится за функцией, не так ли? И соответственно, всё своё замыкание.

Вся эта королевская рать будет жива в памяти ровно до тех пор, пока функция где-то сохранена в переменную. Особенно радует const в этом плане.

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

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

Типичная утечка памяти.

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

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

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

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

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

О каких «последствиях» вы говорите? :)

Видимо о тех, которые не сможет почистить их gc.

Для начала, есть разница между cache и cash. За такое именование казнят утоплением в ссаных тряпках.

Далее: код не читаемый в принципе. Прикинь как такое читать, когда код на сотни тысяч строк пойдёт?

КОММЕНТЫ где? Мы по коду должны догадаться, как оно ДОЛЖНО работать?
Рискну предположить: ты хотел собрать функцию, которая будет что-то накапливать, не важно каким способом. Суть того что ты хочешь, и почему это не может и не должно работать: ты хочешь хранить ЧТО-ТО, не связанное вообще ни с чем, то есть прямо в прототипе?

Ели бы были способы собрать адскую машинку, в 100% в продакшене за них кастрируют, просто потому что ты пишешь утечку памяти. Разумеется, в реальности такие данные считаются мусором, и сборщик мусора — единственный кто к ним доступ имеет.

Как правильно: чтобы функция была честным объектом, ты должен возвращать не значение, а саму функцию. Но чтобы она была взята из какого-то объекта! И вот тогда с точки зрения движка, ты за эти яйца держишь замыкание объекта, а он в свою очередь — объект, созданный как зона видимости функции.

var solution = function(){ 
  function sum(num){
    let factorial = num; 
    for(let i = 1; i < num; i++) factorial *= i; 
  return factorial}
  let sumResult = 0;
  let cash = []; 
  function casher(num){cash.push(num); sumResult+=sum(num); return cash}
return {casher} }().casher;
// Тестим
console.info(solution(5));
console.info(solution(1));

Чувак скорее всего просто хочет кэшировать результаты выполнения функции подсчёта факториала. Для этого нужен сам кэш, и немного преобразовать функцию вычисления: 1) сначала проверяется наличие результата в кэше по аргументу 2) если его нет, вычисляется факториал и заносится в кэш. Далее всё это обволакивается в какую-то оболочку, если не хочет глобальных переменных. Как-то так:

let solution = function() {

let cache = {}
function factorial(n) {
    if (cache[n]) {
        return cache[n]
    } else {
        let f = n
        for (let i = 2; i < n; i++) f *= i
        cache[n] = f
        return f
    }
}

return factorial
}

// test
let f = solution()
console.log(f(5))
console.log(f(1))
Кэш не сохронят свой предыдущий state при новом вызове функции(((

jsbin.com/...​usizamudo/edit?js,console

судя по названию родительской функции это либо собес либо codewars

const memoizer = function(memo, formula){
    const recur = function(n){
        let result = memo[n];
        if (typeof result !== 'number'){
            result = formula(recur, n);
            memo[n] = result;
        }
        return result;
    };
    return recur;
};
const factorial = memoizer([1,1], function(recur, n){
    return n*recur(n-1);
});
7chan.org/...​e_Good_Parts_May_2008.pdf
Советую купить или скачать данную книгу.

код — полный бред. объясните на словах что за задачу вы решаете.

casher

Судячи з цього йому потрібно кешувати.

Нельзя, потому как реализация не kosher

Подписаться на комментарии