временно лежит на страница
так как кнопка появляется по ховер событию, то на мобильном ее мы не увидим
поэтому я решил чтобы при клике на squares__item клик передовался на кнопку
но почему то вылетает ошибка Maximum call stack size exceeded
Содержание
Проблема
Некоторое время назад в работе над клиентской (javascript) частью движка josi возникла, кстати, достаточно часто встречающаяся проблема переполнения стека:
Uncaught RangeError: Maximum call stack size exceeded (google chrome)
В статье рассматривается решение без использования setTimout или setInterval.
Причина такого поведения известна и понятна, и в той или иной форме всегда вызвана следующим. Классическая(прямая) рекурсия порождает цепочку последовательных вызовов, что соответственно ведет к наполнению стека вызовов, однако, стек вызовов браузера достаточно мал, в chrome на момент тестирования это 500 вызовов, в safari, если не ошибаюсь, тоже. В любом случае- это предельное значение, а значит его можно превысить и получить exception. Естественно, столь долгое выполнение кода не желательно в принципе, и этого стоит избегать. И все же лично мне не хочеться полагаться на удачу, не смотря на то, что ситуация в которой пришлось столкнуться с проблемой на продакшен возникнуть не должна, я потратил время на изучение данного вопроса.
Решение
Классическим решением (имею ввиду подавляющее количество статей предлагающих его) является использование косвенной рекурсии посредством: setTimeout либо setInterval.
В качестве примера приведу простенькую рекурсивную функция, единственное назначение которой рано или поздно вернуть Вам предел размера стека вместе с exeption о превышении этого предела…
та же бесполезная функция, но теперь теоретически бесконечная, разве что k переполнится
Текущая функция сразу завершается за счет использования для рекурсии посредника setTimout, а следующий вызов выполняется по событию.
Отрицательной стороной такого подхода является его крайне низкая производительность, несмотря на то, что мы указываем нулевую задержку. Вызвана функция будет в зависимости от браузера в среднем не раньше чем через 10 мс. Но ведь мы боремся с превышением стека вызовов, а значит наша функция вызывается сотни раз, что означает потерю в производительности
1 с на каждые 100 вызовов. Детальное тестирование нашел тут.
Самое простое, что пришло в голову — организовать симбиоз из попеременного использования прямого и косвенного вызовов, чтобы при достижении некоторого значения счетчика прерывать стек косвенным вызовом. Отчасти такое решение сейчас и используется. Но здесь тоже все не так просто, особенно если рекурсия представлена петлей из нескольких функций.
Вот простенький пример отражающий суть такого решения:
В моем коде проблема возникла в шаблонизаторе, который как раз незадолго до этого был переписан согласно новой парадигме. Не хотелось отказываться от принятой архитектуры. В тоже время реальное падение производительности составило 20-30% — что было просто чудовищно. Предложенное выше решение тоже не идеал: сохранялось падение производительности на 5-7%. Это меня не устроило: много гуглил, и напал на то, что нужно.
А это тест от туда же, из которого видно что, предложенный подход, в сравнении с setTimeout 0, гораздо более производительный, что на практике дало не более 3% падения производительности в моем случае…
Данное решение основано на связке window.postMessage и element.addEventListener, для меня достаточно кроссбраузерно (ie8+).
Я переработал функцию из приведенной выше статьи в AMD модуль. Возможно, кому-то это будет полезным…
Читают сейчас
![]()
Похожие публикации
- 1 октября 2016 в 02:49
Закат Stack Overflow
Подробности завершения периода беты Stack Overflow на русском языке
Автоматическое дополнение JS-кода из базы Stack Overflow
Вакансии AdBlock похитил этот баннер, но баннеры не зубы — отрастут
Комментарии 29
В предыдущей версии в основе лежал цикл, который перебирал символы шаблона, выискивал включения, и заменял на что нужно, ну вообщем как обычно.
— здесь я конечно хватил немного. Я имел ввиду, что принцип обработки шаблона так или иначе связан с перебором символов содержимого, по крайней мере, я не могу назвать какой либо другой возможный принцип поиска, допустим необходимого нам символа в тексте, без использования каких либо техник индексации и тд. Как правило(по крайней мере я бы точно сделал так), используется конструкция вида:
Где, f_var_replacer возвращает некоторое значение, помещаемое на место того что нам нужно заменить. Однако, насколько я знаю regexp — это конечный автомат, который так или иначе выполняет полный перебор всех символов в один проход.
Раньше с целью лучшего понимания, а теперь скорее с целью получения большего контроля над процессом, я выполняю данную операцию вручную — те в цикле перебираю символы строки, задавая на входе набор состояний, в которых может находится парсер, и обрабатываю текст. Не ручаюсь за производительность, решения. К сожалению в потоке работы, так и не протестировал (но обязательно сделаю) скорость работы regexp и реализованного алгоритма (думаю regexp безоговорочно выиграет, поскольку свой подход считаю очень примитивным).
Теперь что касается семи ста вызовов. Если честно, хотел бы расписать все по полочкам, с самого начала. Проект активно развивается и, сейчас, я наконец таки взялся за написание общедоступной документации. Где хочу расписать архитектуру, в том числе шаблонизатор, поскольку в него я вложил очень много труда. Конечно, я не претендую, на оригинальность, или самое лучшее решение, но удалось сделать то, что было запланировано, и это работает!
700 вызовов, это не чистая цифра, и складывается она из 1 вызова эффективной функции, +3 накладных (диспетчер задач). Отсюда и цифра. Реально это 150-200 вызовов. В результате оптимизации число накладных вызовов было снижено до 2.
Я не хочу углубляться в подробности отрывочно, поскольку не удастся передать суть, и обосновать те или иные решения… Статьи уже в работе. Буду рад если информация покажется Вам интересной и принесет пользу!
Если не трудно приведите пример.
Bижу это так:
В любом случае это не цикл while…
В свое время для асинхронной эвалюации абстрактного синтаксического дерева, я также искал решение данной проблемы, в результате набрел вот на это: zef.me/3420/async-foreach-in-javascript и по мотивам сделал свой собственный вариант forEachAsync:
500 вызовов это более чем достаточно, с огромным запасом.
Если учесть что рекурсия оправдана лишь там где идет ветвление.
Ведь не зря существует такое понятие как Хвостовая оптимизация рекурсии, ее смысл в том, что последний рекурсивный вызов функцией самой себя ВСЕГДА может (и по хорошему должен) быть повешен на цикл. Исходя из этого если у вас ветвление к примеру всего 2 рекурсивных вызова с глубиной стека 500, то функция отработает 2**500 раз (это число со 150 десятичными нулями) Т.е. глубина стека для рекурсивного алгоритма это 500я степень какого то коэффициента ветвления, поэтому это Очень много. Даже если представить странный случай «разреженной рекурсии» где второй вызов случается всего в 10% случаев — количество вызовов будет числом с 20ю нулями.
Т.о. проблема не в том, что стек мал. Да пусть он хоть стремится к бесконечности, если рекурсия была бы действительно оправдана, время выполнения вашего алгоритма было бы астрономическим… Вы просто используете рекурсию в алгоритме в котором она является антипатерном.
И «рекурсивный вызов» в setTimeout’е это не рекурсия, потому что это не «вызов», а «добавление события» в eventloop, это совершенно архитектурно разные вещи, вы просто тянете «визуально рекурсивный» паттерн туда где он не нужен. Есть async идеально подходящий для этого. Для кроссбраузерности есть промисы (которые в крайнем случае прекрасно полифилятся на callback’ах).
Если у вас проблема с переполнением стека, при дебаге оправданных рекурсивных функций можно добрасывать в параметр функции maxCallLength=const уменьшая его на каждый вызов. И обрабатывая кейс когда он стал нулевым. В иных случаях если при рекурсии происходит переполнение — у вас что то не так с алгоритмом.
tl;dr что было актуально раньше — не актуально сейчас.
Не смешивайте две стороны: рекурсия как организация обработки данных и рекурсия как способ управления потоком исполнения.
Рекурсивные алгоритмы могут быть реализованы по-разному, и сохранение контекста в стек с передачей управления в функцию — только одна из реализаций. Как Вы заметили, некоторые рекурсивные функции легко представляются в виде цикла. Как говорилось в одном коане, цикл — это рекурсия для бедных. Обычно, говоря о хвостовой рекурсии, понимают оптимизацию, производимую компилятором.
В статье рассматриваются рекурсивные функции с хвостовой рекурсией, производящие только побочные эффекты, но в принципе можно распространить на любые функции-продолжения, а также на циклы.
Насколько я понимаю, все Ваши расчёты исходят из того, что рекурсивная функция не содержит продолжений, то есть мы не можем прямолинейно размотать рекурсивное исполнение функции в цикл. А это именно тот случай, который рассматривается в статье. Впрочем, в Ваших расчётах не учитывается рекурсия с переменной глубиной вызовов, например, обход в глубину графа с радиусом >500, в котором общее число вызовов примерно равен числу вершин.
Замечу, что истинная ценность метода состоит не в обходе ограничения глубины стека, а в разбиении вычислений на несколько частей, переход между которыми включал прерывание вычислений. В те годы вычисления в основном потоке напрочь блокировал интерактивность интерфейса и это было критично для тяжелых вычислений, проводимых в цикле или рекурсией (см. например, комментарий, который о вычислении в цикле)
И «рекурсивный вызов» в setTimeout’е это не рекурсия, потому что это не «вызов», а «добавление события» в eventloop, это совершенно архитектурно разные вещи
Техника, описанная в статье, конечно, для этого не подходит, но допустим, Вам нужно обойти граф и для этого Вы составили функцию, которая оказалась по всем формальным критериям, рекурсивной. Далее Вы её как-то реализовали в коде. Какая разница, как там под капотом это будет реализовано: через стек, циклом или событиями eventloop? JS увы, требует в коде явно решать такие вопросы, которые большинству программистов не интересны.
Наконец, мы же справедливо можем назвать рекурсивной любую функцию, в теле которой упоминается она сама.
Есть async идеально подходящий для этого. Для кроссбраузерности есть промисы (которые в крайнем случае прекрасно полифилятся на callback’ах).
В 2013 промисов в действующем стандарте ES5 не было, async/await (которые суть сахар над промисами) тем более. С промисами же решение становится почти тривиальным.
Надо отметить, что в настоящее время задача неактуальна, а решение — тем более. Так что предлагаю с уважением похоронить статью.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.
Я использую файл библиотеки JavaScript Direct Web Remoting (DWR) и получаю сообщение об ошибке только в Safari (на рабочем столе и в iPad)
В нем говорится
Максимальный размер стека вызовов.
Что именно означает эта ошибка и она полностью прекращает обработку?
Также любое исправление для браузера Safari (Фактически на iPad Safari , он говорит
JS: выполнение превысило время ожидания
который я предполагаю, является той же проблемой стека вызовов)
Это означает, что где-то в вашем коде вы вызываете функцию, которая в свою очередь вызывает другую функцию и т.д., пока вы не достигнете предела стека вызовов.
Это почти всегда из-за рекурсивной функции с базовым случаем, который не выполняется.
Просмотр стека
Рассмотрим этот код.
Вот стопка после нескольких вызовов.

Как вы можете видеть, стек вызовов растет до тех пор, пока он не достигнет предела: размер жесткого диска браузера или исчерпание памяти.
Чтобы исправить это, убедитесь, что ваша рекурсивная функция имеет базовый регистр, который может быть выполнен.
Иногда вы можете получить это, если вы случайно импортируете/внедрите один и тот же файл JavaScript дважды, стоит проверить это на вкладке ресурсов инспектора.
В моем случае я отправлял элементы ввода вместо их значений:
Это заморозило мою вкладку Chrome до такой степени, что даже не отображало диалоговое окно "Ждать/убивать", когда страница перестала отвечать на запросы.
В вашем коде есть рекурсивный цикл (т.е. функция, которая в конечном итоге вызывает себя снова и снова, пока стек не будет заполнен).
В других браузерах есть либо большие стеки (поэтому вместо этого вы получаете тайм-аут), либо по какой-то причине они проглатывают ошибку (возможно, плохо поставленный try-catch).
Используйте отладчик для проверки стека вызовов при возникновении ошибки.
В моем случае я преобразовал большой массив байтов в строку, используя следующее:
bytes содержало несколько миллионов записей, которые слишком велики для размещения в стеке.
Проблема с определением стекового потока иногда заключается в том, что трассировка стека раскручивается, и вы не сможете увидеть, что на самом деле происходит.
Я нашел некоторые из новых инструментов отладки Chrome полезными для этого.
Перейдите на Performance tab , убедитесь, что Javascript samples включены, и вы получите что-то вроде этого.
Это довольно очевидно, где переполнение здесь! Если вы нажмете на extendObject вы сможете увидеть точный номер строки в коде.

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

Еще один полезный трюк, если вы не можете найти проблему, — это поместить множество операторов console.log там, где вы думаете, что проблема. Предыдущий шаг выше может помочь вам в этом.
В Chrome, если вы неоднократно выводите идентичные данные, он будет отображаться так, как показано, где проблема более ясна. В этом случае стек достиг 7152 фреймов, прежде чем окончательно рухнул:

В моем случае событие click распространялось на дочерний элемент. Итак, мне пришлось поставить следующее:
по событию клика:
Проверьте сведения об ошибках на консоли панели инструментов Chrome dev, это даст вам функции в стеке вызовов и направит вас к рекурсии, вызывающей ошибку.
Если по какой-то причине вам нужен бесконечный процесс/рекурсия, вы можете использовать webworker в отдельном потоке. http://www.html5rocks.com/en/tutorials/workers/basics/
если вы хотите манипулировать элементами dom и перерисовывать, используйте анимацию http://creativejs.com/resources/requestanimationframe/
В моем случае два модажа jQuery отображались друг на друга. Предотвращение этой проблемы.
Почти каждый ответ здесь утверждает, что это может быть вызвано только бесконечным циклом. Это неправда, в противном случае вы могли бы перегрузить стек через глубоко вложенные вызовы (не говоря уже о том, что это эффективно, но это, безусловно, возможно). Если у вас есть контроль над вашей виртуальной машиной JavaScript, вы можете настроить размер стека. Например:
Недавно мы добавили поле на сайт администратора, над которым мы работаем — contact_type. easy right? Что ж, если вы вызываете select "type" и пытаетесь отправить его через вызов jquery ajax, это завершится неудачно с этой ошибкой, скрытой глубоко в jquery.js. Не делайте этого:
Проблема в том, что type: type — я считаю, что мы называем аргумент "type" — наличие значения переменной с именем type не является проблемой. Мы изменили это на:
И переписал some_function.php соответственно — проблема решена.
Оба вызова идентичного кода ниже, если оно уменьшено на 1, работают в Chrome 32 на моем компьютере, например. 17905 по сравнению с 17904. Если они выполняются так, они выдают ошибку "RangeError: Максимальный размер стека вызовов". Похоже, что это ограничение не является жестким, а зависит от оборудования вашего аппарата. Похоже, что если вызывается как функция, этот самоналоженный предел выше, чем если он вызывается как метод, то есть этот конкретный код использует меньше памяти при вызове как функцию.
Вызывается как метод:
Вызывается как функция:
вы можете найти свою рекурсивную функцию в браузере crome, нажмите ctrl + shift + j, а затем вкладку source, которая дает вам поток компиляции кода, и вы можете найти, используя точку останова в коде.
Я также столкнулся с подобной проблемой, вот подробности при загрузке логотипа с помощью выпадающего меню для загрузки логотипа
до исправления моего кода:
Ошибка в консоли:

Я решил это, удалив onclick="$(‘#filePhoto’).click()" из тега div.
Я столкнулся с той же проблемой Я разрешил это, удалив имя поля, которое использовалось дважды на ajax например
Я знаю, что эта ветка старая, но я думаю, что стоит упомянуть сценарий, в котором я нашел эту проблему, чтобы она могла помочь другим.
Предположим, у вас есть такие вложенные элементы:
Вы не можете манипулировать событиями дочернего элемента внутри события его родителя, потому что он распространяется на самого себя, делая рекурсивные вызовы, пока не будет сгенерировано исключение.
Так что этот код не удастся:
У вас есть два варианта, чтобы избежать этого:
- Переместите ребенка наружу от родителя.
- Примените функцию stopPropagation к дочернему элементу.
У меня была эта ошибка, потому что у меня было две функции JS с одинаковым именем
Если вы работаете с картами Google, проверьте, передаются ли последние значения в new google.maps.LatLng правильного формата. В моем случае они передавались как неопределенные.
Проблема в моем случае заключается в том, что у меня есть дочерний маршрут с тем же путем, что и родительский:
Поэтому мне пришлось убрать линию детского маршрута
в моем случае я получаю эту ошибку при вызове ajax, и данные, которые я пытался передать этой переменной, не определены, то есть показывает эту ошибку, но не описывает эту переменную, не определенную. Я добавил определил, что переменная n получила значение.
Проверьте, есть ли у вас функция, которая вызывает сама себя. Например
Это также может привести к Maximum call stack size exceeded :
Но будьте осторожны: при использовании применять этот способ вы рискуете превысить ограничение длины аргумента движка JavaScript. Последствия применения функции со слишком большим количеством аргументов (например, более десятков тысяч аргументов) варьируются в зависимости от движков (JavaScriptCore имеет жестко запрограммированный предел аргументов 65536), потому что это предел (в действительности, даже характер любого чрезмерно большого стека) поведение) не уточняется. Некоторые двигатели будут выбрасывать исключения. Более пагубно, другие будут произвольно ограничивать количество аргументов, фактически передаваемых прикладной функции. Чтобы проиллюстрировать этот последний случай: если бы такой механизм имел ограничение в четыре аргумента (фактические пределы, конечно, значительно выше), это было бы так, как если бы аргументы 5, 6, 2, 3 были переданы для применения в приведенных выше примерах, а не полный массив.





