Python applications, ASGI, Kopf, testing of Elasticsearch на Python fwdays'20 | Online

Різниця в об’явленні змінної в JavaScript

В коді нижче , функція throttle(f, ms) повинна повертати обгортку навколо f , яка буде виводити результат f раз в ms мілісекунд
(результатом будет останній виклик функції за період ms мілісекунд).

Даний код працює правильно.Але,якщо оголосити змінну arg на початку функції wrap() ,тоді результат буде іншим.(Результатом буде перший виклик,а не останній).
Так в чому причина ? Яка різниця де оголосити змінну, якщо в обох випадках вона доступна зсередини функції wrap ?

function throttle(f, ms) {
    var time , arg ;
    return function wrap() {
        arg = arguments ;
        var context = this ;

        if (time != undefined ) return ;
        time = setTimeout(function(){
            f.apply(context, arg) ;
            time = undefined ;
        },ms );
    }
}
function sum(a, b) {
    alert(a+b) ;
}
sum = throttle(sum, 1000) ;
sum(1, 0 ) ;
sum(2, 0) ;

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

Зачем придумывать велосипеды ещё и с замыканиями? www.learnrxjs.io/...​s/filtering/throttle.html

Throttling and Debouncing in JavaScript Не думал что таким примитивом нужно делиться на доу, но глядя на комментарии вижу что нужно.

Чем setInterval не устраивает для задачи вывести что-то строго каждые X ms ?
Или это решение задачи с learn.javascript.ru/settimeout-setinterval ?

вообще то это не чаще, чем раз в Х ms
полезно для всяких онскроллов и т.д.

Само собой, «не чаще чем», но без учёта времени выполнения самого запроса, потому относительно более-менее равномерно и предсказуемо, чем если через setTimeout если нужно «буде виводити результат f раз в ms мілісекунд»

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

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

Тогда это несколько иначе реализуется: ставится таймстемп последнего удачного вызова, делается буфер на N событий (например одно) и никакого цирка с setTimeout

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

Тем более блин, на полном серьезе обсуждать реализацию throttle/debounce?? Можно скопировать готовую реализацию в интернете или вообще воспользоваться npm-модулем (они есть и для throttle, и для debounce, и даже с целой документацией по использованию). Ты не будешь каждый день смотреть в код этой функции, оно пишется максимум один раз и потом просто используется.

Тем более блин, на полном серьезе обсуждать реализацию throttle/debounce?? Можно скопировать готовую реализацию в интернете

Там таки есть что обсуждать. Например, мне встречались такие варианты (это всё не для JS, но легко себе представить перенос соответствующих задач в браузер):
1. Нужно жёстко соблюсти минимальный интервал между завершением какой-то периодической работы и стартом следующей итерации, причём отследить все варианты завершения предыдущей, включая аварийные (может быть слишком развесисто в коде).
2. Против некоторых тяжёлых работ, которые могут долго идти, а могут взрываться мговненно — считать кумулятивное среднее времени работы итерации и делать задержку до следующей итерации обратно этому времени (фактически там по каждому завершению получается что-то вроде
const alpha = 0.95; avg_spent = avg_spent * alpha + last_time * (1-alpha); schedule_next(now() + 30/(1+avg_spent));
)
3. Debounce с максимальным ограничением в 2*t1 от первого запроса, но каждый следующий сокращает время до (t1+t_prev)/2, и видами более приоритетных запросов, которые могут вызвать немедленный старт, или сильнее ускорить.
4. Debounce с минимальным временем и token bucket на частоту срабатываний.

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

Ну так learn млять, а не вопросы задавай. Если это учебный пример — бери и разбирайся.

Только СНАЧАЛА разберись со средствами логирования и отладки. Без этого ты дальше HelloWorld не двинешься.

function throttle(f, ms) {
    return function wrap() {
    var time , arg ;
        arg = arguments ;
        var context = this ;

        time = setTimeout(function(){
            f.apply(context, arg) ;
        },ms );
    }
}
function sum(a, b) {
    alert(a+b) ;
}
sum = throttle(sum, 1000) ;
sum(1, 0 ) ;
sum(2, 0) ;

Вот так вот работает. Дело в том, что в ващем примере arg успевает перезаписаться, так как переменная общая, а вызов sum(2, 0) — идет перед вызовом таймаута

Если вы объявляете time внутри wrap, то throttle точно не получится

условия задачи не описаны. Если нужно — можно time перенести на уровень выше

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

бедные формошлепы. на какие только извращения не приходится идти

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

dou.ua/...​rums/topic/23794/#1329901
говорят не только формошлепы
пан видать только круды и шлепает ниче сложнее не видел?

У вас в

ms );

пробіл відсутній, після скобки. Хіба ж так можна знущатися над виглядом? :D

Я лише додам: ніколи такої хєрні не пишіть. Окрім того, що код має виконуватися правильно, він має ще й читатися швидко і однозначно.

Ви маєте коментувати. Перш за все, ті змінні які назначаєте. Так само, ви маєте давати їм зрозумілі імена.

sum — тут очікується повернення сумми, а зовсім не alert. Взагалі ніколи не використовуйте alert, забудьте про існування цієї функції. Вона робить зовсім неочевидну річ — це модальне вікно, вона має припинити виконання будь-яких скриптів. Раніше так і відбувалося, але ж не з багатопотоковими движками сучасних браузерів. Ви не можете знати, що саме застопорить alert, а що продовжить виконуватися. То ж просто забудьте про alert.

throttle — що має робити? Це зовсім неочевидно. Те що робить ми бачимо, але ти маєш писати що повинен робити. Інакше, якщо там помилка — хтось інший може й тиждень витратити, може й місяць, але не знайти. Бо незрозуміло що очікувати, що із чим порівнювати.

time — не зрозуміле призначення змінної за задумом. Оскільки видимість зовнішня, ти маєш писати комент, та давати більш зрозуміле ім′я.

Імена взагалі краще не створювати, якщо ти не збираєся їх бачити десь далеко. Всякий раз, коли тільки можливо, пиши анонімні функції. Їх значно легше рефакторити: одразу видно що вони ніде більше не використовуються, ні з чим більше не пов′язані.
wrap — змінна не потрібна, напиши просто «return function» і це буде зрозуміло.

Замикання — то взагалі дуже тяжка конструкція для оперативи. Зробити це досить 1 строчки, а от розплутати все те сміття майже неможливо. Наплоди таких у циклі — і матимеш досить складно вловимий memory leak.

Головна твоя проблема — це конкуренція timeout-механізму JS-движка із непередбачуваним alert і твоєю поведінкою. Вистачає одного alert щоб зірвати нормально поведінку майже усіх механізмів на таймаутах.

На реальній галері вас каратимуть не за неефективний код (на це начхати), але за незрозумілий 100%. Ніхто не має досліджувати ваш код дослідами, він має бути зрозумілим одразу. І заплутаним чим менше — тим краще.

Со всем в целом согласен, кроме

throttle — що має робити? Це зовсім неочевидно

Вообще-то throttle довольно общепринятный термин, ничего в нём непонятного нет. Единственное что имена переменных нужны более очевидныеб например method и timeout вместо f и ms. Хотя jsdoc не навредил бы. Ну и это я так понимаю не боевой код, а код для изучения js.

Замикання — то взагалі дуже тяжка конструкція для оперативи. Зробити це досить 1 строчки, а от розплутати все те сміття майже неможливо. Наплоди таких у циклі — і матимеш досить складно вловимий memory leak.

Ну так

На реальній галері вас каратимуть не за неефективний код (на це начхати), але за незрозумілий 100%. Ніхто не має досліджувати ваш код дослідами, він має бути зрозумілим одразу.

Как раз замыкания позволяют избежать простыней и для того кто знает вполне понятны. А знать замыкания сейчас относят к базовым требованиям.

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

Два пишем, три в уме, полгига оперативы в жопе замыкании.

Нифига. Логику нужно разворачивать так, чтобы узлов в принципе не было. А в замыкания отправлять только объекты с большим временем жизни, то есть те которые после выполнения задачи будут ещё живы. Или же с тем же жизненным циклом, что и у всей программы.

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

Throttle — это общеупотребительный термин для тебя, как русскоязычного. Если бы ты свободно владел английским, он бы значил совершенно иное. Например, индуктивность, или клапан. А общеупотребительное значение этого слова — душить, придушать. Однокоренное со словом throat — горло.
Потому, объявляя переменную, надо писать что она делает. В данном случае я ожидал, что эта функция ограничивает количество итераций в единицу времени. А она всего лишь даёт задержку, да ещё и не совсем понятным образом пропускает циклы.

Всегда нужно пояснять ЗАЧЕМ это делается, то есть НАМЕРЕНИЕ. Иначе ошибка будет неуловимой — ведь никто не знает как нужно! А значит если в целом куске кода есть ошибка, или же просто есть подозрение — всё придётся переписать заново!

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

Что там тяжелого в понимании, если ты знаком с замыканиями?

Throttle — это общеупотребительный термин для тебя, как русскоязычного. Если бы ты свободно владел английским, он бы значил совершенно иное. Например, индуктивность, или клапан. А общеупотребительное значение этого слова — душить, придушать. Однокоренное со словом throat — горло.

А cookies — это еда такая. А замыкание бывает в электрике. А ещё совпадение это сов падение. И в больнице ждёт Psycho The Rapist. Но в контексте программирования троттлинг имеет определенное значение.

В данном случае я ожидал, что эта функция ограничивает количество итераций в единицу времени.

И она таки это делает. Правда криво, но делает. И это не боевой код и никто не говорит что нужно так писать.

Видеть где ты их плодишь — вот что сложно. В большинстве случаев время жизни замыкания весьма коротко. Но ты не представляешь, чего я встречал в реальном коде. И хрен же выловишь при code review, это порождает необходимость всё тестить.

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

Cookies — это и есть еда. Термину специально дали смешное название. Но что позволено Юпитеру (Майкрософту во времена монополии) — с тем джуну лучше не быковать.

Что там тяжелого в понимании, если ты знаком с замыканиями?

Везде есть какая-то злобная специфика. В случае JS это хотя бы взаимодействие с this. Ещё надо помнить, где и как ты можешь менять переменные, что тебе проброшены в замыкание (у меня одновременно как минимум Python, C++, регулярно пробегают Erlang и Java, и у них у каждого свои тараканчики). Или, где в замыкании захватывается одна переменная, а где — целый фрейм (а если в нём что-то тяжёлое, то ни RC, ни GC этого не увидят).
Может, при написании всегда на одном языке такие вещи с ходу ловятся уже на автомате, у меня такого опыта нет.

Всякий раз, коли тільки можливо, пиши анонімні функції. Їх значно легше рефакторити: одразу видно що вони ніде більше не використовуються, ні з чим більше не пов′язані.
wrap — змінна не потрібна, напиши просто «return function» і це буде зрозуміло.

Анонімні функції важче дебажити, в порівнянні з іменованими. Тобто якщо навіть в коді логіка не змінюється return function() { } чи return function someName() { }, дебажити краще другий варіант, хоча — так, читати краще перший.

Анонімні функції взагалі не дебажать. Вони або перевіряються як чорна скринька, або ж отримують ім′я там, куди їх передають в якості параметра.

Приклад — Promise. Це типова анонімна конструкція. Ніхто не дебажить те, що кладуть у Promise, дебажать разом із самим викликом.

В чому вигода: не копирсатися в малих деталях, оперувати лише великими фрагментами. Якщо ж треба розібратися в малому — то розбиратися лише в ньому, перш за все уважним прочитанням коду, а якщо це неможливо — навіть переписати з нуля цілий фрагмент, якщо є розуміння що він робить.

Що не так з іменами: у людей нема в пам′яті червоно-чорного дерева під імена, так само нема швидких механізмів створення та знищення об′єктів пам′яті. Створюючи ім′я, ти задіюєш ресурс людини. Машині то все пофіг. Якщо імен багато — код читати важко, навіть легко перетнути межу неможливого, коли затрати на розуміння ростуть по експоненті, і те що не можна зрозуміти за 2-4 години — перетворюється на два тижні.

Анонімні функції взагалі не дебажать. Вони або перевіряються як чорна скринька, або ж отримують ім′я там, куди їх передають в якості параметра.

Коли читаєш call stack, краще бачити імена, ніж «anonfunc7c4d55ab». Іноді це критично (коли така функція на верхівці стеку — тільки назва може підказати, хто і коли її породив).

Створюючи ім′я, ти задіюєш ресурс людини.

Ніякого задіяння ресурсу. Імʼя просто буде після цього грепано в сорцах. Навіть генерація його uuid’ом краще, ніж відсутність.

Взагалі ніколи не використовуйте alert, забудьте про існування цієї функції. Вона робить зовсім неочевидну річ — це модальне вікно, вона має припинити виконання будь-яких скриптів.

Тоді гарно було б і альтернативу назвати — console.log() чи що там замість неї буде.

Замикання — то взагалі дуже тяжка конструкція для оперативи.

Ненабагато складніше, ніж все інше, включаючи обʼєкти з декілька рівнями вкладення прототипів :)
Тим більше що в JS без замикань взагалі нічого робити, занадто все на них покладається.

На реальній галері вас каратимуть не за неефективний код (на це начхати), але за незрозумілий 100%.

+1. Якось дивне загортання у фольгу :)

Коли Ви декларуєте функцію всередині функції, та використовуєте локальні змінні зовішньої функції, виникає замикання. Змінна arg, задекларована в throttle, буде доступна для кожного виклику wrap та обробника в setTimeout. Ви зробили замикання на неї. Тому усі посилання будуть працювати з останньою версією. Коли Ви декларуєте arg всередині wrap, обробник в setTimeout робить замикання на той інстанс, котрий він бачить в певний момент. Як і в першому випадку. Але з-за того, що time != undefined, пересмикування на свіжішій інстанс не відбувається. Тому Ви бачите лише перше значення. Якщо Ви будете викликати sum зсередини таймеру з довшим інтервалом, Ви побачите останні аргументи.

аааа за що ви так з жаваскриптом

Якщо цю жесть написали ви (як джуніор) щоб розібратись з особливостями роботи функцій в JavaScript, то це ще куди не йшло... Але якщо це вас попросили розібрати на співбесіді, то не раджу йти в таку компанію, там вас нічому хорошому не навчать.

По задачці. Ось цим sum = throttle(sum, 1000); — ви встановлюєте функцію і час для setTimeout. Ось цим sum(1, 0); — ви спочатку встановлюєте значення для вашої змінної arg, але тут же перевстановлюєте його другим викликом sum(2, 0);, оскільки функція, передана в setTimeout, ще не встигла відпрацювати, а змінна arg видима для обох викликів.

Якщо змінну arg оголосити на початку функції wrap, то ця змінна буде видима лише в поточному виклику wrap, причому функція для setTimeout відпрацює лише за перше встановлення змінної time, оскільки у вас є умова if (time != undefined ) return;.

До речі, ось такою простою функцією можна згладити піки кількості викликів певної функції до прийнятного timeout:

var nextTime = 0, timeout = 1000;
 
function throttle(callback) {
  nextTime += timeout;
  
  setTimeout(function() {
      nextTime -= timeout;
      callback();
    },
    nextTime
  );
}

Користуватись можна так:

function callback() { console.log('called callback!') }
throttle(callback);
throttle(callback);
throttle(callback);
throttle(callback);
throttle(callback);
throttle(callback);

Это стандартная задачка на собеседованиях. Вам нужно хорошо разобраться, как работает JS, а в особенности замыкания и асинхронность. Похожая проблема разбирается в данной статье: habr.com/...​ompany/ruvds/blog/340194

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

Але навіть якщо змінна arg буде ініціалізовуватись при кожному виклику,чому setTimeout получає тільки перше її значення ?

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

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

Потому что при последующих вызовах до setTimeout дело не доходит из-за if (time != undefined ) return ;

Это сложно объяснить на словах, но я попробую.

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

Что происходит при выполнении кода:

  1. Вызывается функция throttle, которая резервирует в памяти адреса для хранения значений timeout и arg, а затем создает и возвращает функцию wrap
  2. При вызове созданной функции wrap:
    1. В адрес arg записываются аргументы с которыми вызвали wrap (очень важно отметить что это происходит независимо от значения переменной timeout, то есть при каждом вызове — и при первом, и при втором)
    2. Выделяется новый адрес в памяти, context, и в него сохраняется значение this (тоже может приводить к странному поведению в сложных системах, context следовало бы объявлять там же где и arg)
    3. Если по адресу timeout ничего нет, то создается анонимная функция, которая будет вызвана через промежуток времени. А в этой анонимной функции считывается значение по адресу arg и передаётся в функцию f

Если var arg; перенести в функцию wrap, то при каждом вызове адрес будет новый и чтение в той анонимной функции, которую мы передаём в setTimeout, будет происходить из него. И это было бы на мой взгляд более корректной реализацией throttle. А ещё оптимальнее — перенести if (time != undefined ) return ; в начало wrap

Советую погуглить throttle и debounce, чтобы понять зачем это вообще нужно (если не знаете) и посмотреть на разные варианты реализации.

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