JavaScript бесконечный цикл? - PullRequest
37 голосов
/ 29 апреля 2011

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

Код, который я сейчас использую, -

window.onload = function start() {
    slide();
}
function slide() {
    var num = 0;
    for (num=0;num<=10;num++) {
        setTimeout("document.getElementById('container').style.marginLeft='-600px'",3000);
        setTimeout("document.getElementById('container').style.marginLeft='-1200px'",6000);
        setTimeout("document.getElementById('container').style.marginLeft='-1800px'",9000);
        setTimeout("document.getElementById('container').style.marginLeft='0px'",12000);
    }
}

Без этой вещи она проходит один раз. Когда я вставляю for, он либо блокирует Firefox, либо просто зацикливается один раз. Я уверен, что это действительно простая вещь, и даже если она должна быть зациклена 1000000 раз или что-то, а не бесконечно, для меня это подойдет.

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

РЕДАКТИРОВАТЬ: я думаю, что причина его замораживания в том, что он выполняет код все сразу, а затем просто сохраняет его в кэше или что-то в этом роде. То, что я хочу сделать, это пройти через это один раз, а затем начать с вершины снова, что я всегда думал, где и где. В сценарии «пакетной» (командной строки) это можно сделать с помощью команды «GOTO». Я не знаю, есть ли эквивалент в JS или нет, но это действительно моя цель.

Ответы [ 10 ]

63 голосов
/ 29 апреля 2011

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

window.onload = function start() {
    slide();
}
function slide() {
    var num = 0, style = document.getElementById('container').style;
    window.setInterval(function () {
        // increase by num 1, reset to 0 at 4
        num = (num + 1) % 4;

        // -600 * 1 = -600, -600 * 2 = -1200, etc 
        style.marginLeft = (-600 * num) + "px"; 
    }, 3000); // repeat forever, polling every 3 seconds
}
19 голосов
/ 29 апреля 2011

Вы не хотите while(true), это заблокирует вашу систему.

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

function start() {
  // your code here
  setTimeout(start, 3000);
}

// boot up the first call
start();
8 голосов
/ 29 апреля 2011

Вот хорошее, аккуратное решение для вас: ( также посмотрите живую демонстрацию -> )

window.onload = function start() {
    slide();
}

function slide() {
    var currMarg = 0,
        contStyle = document.getElementById('container').style;
    setInterval(function() {
        currMarg = currMarg == 1800 ? 0 : currMarg + 600;
        contStyle.marginLeft = '-' + currMarg + 'px';
    }, 3000);
}

Поскольку вы пытаетесь учиться, позвольте мне объяснить, как это работает.

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

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

Мы передаем анонимную функцию в setInterval, которая будет выполнять нашу работу за нас,Первая строка:

currMarg = currMarg == 1800 ? 0 : currMarg + 600;

Это троичный оператор .Он будет присваивать currMarg значение 0, если currMarg равно 1800, в противном случае он будет увеличивать currMarg на 600.

Со второй строкой мы просто присваиваем нашувыбрано значение container s marginLeft, и все готово!

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

4 голосов
/ 29 апреля 2011

Perhps это то, что вы ищете.

var pos = 0;
window.onload = function start() {
    setTimeout(slide, 3000);
}
function slide() {
   pos -= 600;
   if (pos === -2400)
     pos = 0;
   document.getElementById('container').style.marginLeft= pos + "px";
   setTimeout(slide, 3000);
}
2 голосов
/ 29 апреля 2011

Вы звоните setTimeout() десять раз подряд, поэтому срок их действия истекает почти в одно и то же время. То, что вы на самом деле хотите, это:

window.onload = function start() {
    slide(10);
}
function slide(repeats) {
    if (repeats > 0) {
        document.getElementById('container').style.marginLeft='-600px';
        document.getElementById('container').style.marginLeft='-1200px';
        document.getElementById('container').style.marginLeft='-1800px';
        document.getElementById('container').style.marginLeft='0px';
        window.setTimeout(
          function(){
            slide(repeats - 1)
          },
          3000
        );
    }
}

Это вызовет slide (10), который затем установит 3-секундный тайм-аут для вызова slide (9), который установит тайм-аут для вызова slide (8) и т. Д. Когда вызывается slide (0), больше нет тайм-ауты будут установлены.

1 голос
/ 29 апреля 2011

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

var current = 0;
var num_slides = 10;
function slide() {
    // here display the current slide, then:

    current = (current + 1) % num_slides;
    setTimeout(slide, 3000);
}

Альтернативой является использование setInterval, которое задает функцию регулярного повторения (в отличие от setTimeout, которая планирует только следующее появление.

1 голос
/ 29 апреля 2011

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

function it_keeps_going_and_going_and_going() {
  it_keeps_going_and_going_and_going();
}

it_keeps_going_and_going_and_going()
0 голосов
/ 30 апреля 2019

Расширение на Ответ Эндера , давайте рассмотрим наши варианты с улучшениями от ES2015.


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

function slide() {
    var num = 0;
    for (num=0;num<=10;num++) {
        setTimeout("document.getElementById('container').style.marginLeft='-600px'",3000);
        setTimeout("document.getElementById('container').style.marginLeft='-1200px'",6000);
        setTimeout("document.getElementById('container').style.marginLeft='-1800px'",9000);
        setTimeout("document.getElementById('container').style.marginLeft='0px'",12000);
    }
}

Однако в действительности происходит то, что ...

  • Цикл «одновременно» создает 44 асинхронных тайм-аута, настроенных на выполнение 3, 6, 9 и 12 секунд в будущем.Аскер ожидал, что 44 вызова будут выполняться один за другим, но вместо этого все они будут выполняться одновременно.
  • Через 3 секунды после завершения цикла для container marginLeft установлено значение "-600px" 11 раз.
  • 3 секунды после этого для marginLeft установлено значение "-1200px" 11 раз.
  • через 3 секунды "-1800px", 11 раз.

И такon.

Вы можете решить эту проблему, изменив его на:

function setMargin(margin){
    return function(){
        document.querySelector("#container").style.marginLeft = margin;
    };
}

function slide() {
    for (let num = 0; num <= 10; ++num) {
        setTimeout(setMargin("-600px"), + (3000 * (num + 1)));
        setTimeout(setMargin("-1200px"), + (6000 * (num + 1)));
        setTimeout(setMargin("-1800px"), + (9000 * (num + 1)));
        setTimeout(setMargin("0px"), + (12000 * (num + 1)));
    }
}

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

Уроки, извлеченные из десятилетнего опыта

Как упоминалось в начале этого ответа, Эндер уже предложил решение, ноЯ хотел бы добавить к этому, чтобы учесть передовую практику и современные инновации в спецификации ECMAScript.

function format(str, ...args){
    return str.split(/(%)/).map(part => (part == "%") ? (args.shift()) : (part)).join("");
}

function slideLoop(margin, selector){
    const multiplier = -600;
    let contStyle = document.querySelector(selector).style;

    return function(){
        margin = ++margin % 4;
        contStyle.marginLeft = format("%px", margin * multiplier);
    }
}

function slide() {    
    return setInterval(slideLoop(0, "#container"), 3000);
}

Давайте рассмотрим, как это работает для начинающих (обратите внимание, что не все это напрямуюсвязан с вопросом):

format

function format

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

format(str, ...args)

... - это шикарная функция, добавленная в ES6, которая позволяет вам делать множество вещей.Я считаю, что это называется оператором спреда.Синтаксис: ...identifier или ...array.В заголовке функции вы можете использовать его для указания аргументов переменных, и он будет принимать каждый аргумент в позиции и после позиции указанного аргумента переменной и помещать их в массив.Вы также можете вызвать функцию с массивом, например, так: args = [1, 2, 3]; i_take_3_args(...args), или вы можете взять объект, похожий на массив, и преобразовать его в массив: ...document.querySelectorAll("div.someclass").forEach(...).Это было бы невозможно без оператора распространения, потому что querySelectorAll возвращает «список элементов», который не является истинным массивом.

str.split(/(%)/)

Я не очень хорошо объясняю, как работает регулярное выражение.JavaScript имеет два синтаксиса для регулярных выражений.Есть ОО-путь (new RegExp("regex", "gi")) и буквальный (/insert regex here/gi).Я испытываю глубокую ненависть к регулярным выражениям, потому что лаконичный синтаксис, который он поощряет, часто приносит больше вреда, чем пользы (а также потому, что они чрезвычайно непереносимы), но есть некоторые случаи, когда регулярное выражение полезно, как этот.Обычно, если вы вызвали split с "%" или /%/, результирующий массив исключит разделители «%» из массива.Но для алгоритма, используемого здесь, мы должны включить их./(%)/ было первым, что я попробовал, и это сработало.Думаю, повезло.

.map(...)

map - это функциональная идиома.Вы используете карту, чтобы применить функцию к списку.Синтаксис: array.map(function).Функция: должна возвращать значение и принимать 1-2 аргумента.Первый аргумент будет использоваться для хранения каждого значения в массиве, а второй будет использоваться для хранения текущего индекса в массиве.Пример: [1,2,3,4,5].map(x => x * x); // returns [1,4,9,16,25].См. Также: фильтр, поиск, уменьшение, forEach.

part => ...

Это альтернативная форма функции.Синтаксис: argument-list => return-value, например (x, y) => (y * width + x), что эквивалентно function(x, y){return (y * width + x);}.

(part == "%") ? (args.shift()) : (part)

Пара операторов ?: - это 3-операндный оператор, называемый троичным условным оператором.Синтаксис: condition ? if-true : if-false, хотя большинство людей называют его «троичным» оператором, поскольку на каждом языке, на котором он появляется, это единственный оператор с 3 операндами, каждый другой оператор является двоичным (+, &&, |, =) или унарным (++, ..., &, *).Интересный факт: некоторые языки (и расширения языков поставщиков, такие как GNU C) реализуют двухоперационную версию оператора ?: с синтаксисом value ?: fallback, который эквивалентен value ? value : fallback, и будут использовать fallback, если value оценивается как ложное.Они называют это оператором Элвиса.

Я должен также упомянуть разницу между expression и expression-statement, так как я понимаю, что это может быть не интуитивно понятно для всех программистов.expression представляет значение и может быть присвоено l-value.Выражение может быть заключено в круглые скобки и не может считаться синтаксической ошибкой.Выражение само может быть l-value, хотя большинство операторов r-values, поскольку единственными выражениями с l-значением являются выражения, сформированные из идентификатора или (например, в C) из ссылки / указателя.Функции могут возвращать l-значения, но не рассчитывают на это.Выражения также могут быть составлены из других, меньших выражений.(1, 2, 3) - это выражение, сформированное из трех выражений r-значения, объединенных двумя запятыми операторами.Значение выражения равно 3. expression-statements, с другой стороны, это операторы, сформированные из одного выражения.++somevar является выражением, поскольку его можно использовать в качестве r-значения в выражении-выражении оператора присваивания newvar = ++somevar; (например, значение выражения newvar = ++somevar является значением, которое присваивается newvar),++somevar; также является выражением-выражением.

Если троичные операторы вообще вас смущают, примените то, что я только что сказал, к троичному оператору: expression ? expression : expression.Тернарный оператор может образовывать выражение или выражение-оператор, поэтому обе эти вещи:

smallest = (a < b) ? (a) : (b);
(valueA < valueB) ? (backup_database()) : (nuke_atlantic_ocean());

являются допустимыми использованиями оператора.Пожалуйста, не делайте последнее, хотя.Вот для чего if.Есть случаи для такого рода вещей, например, в макросах препроцессора C, но мы говорим о JavaScript здесь.

args.shift()

Array.prototype.shift.Это зеркальная версия pop, якобы унаследованная от языков оболочки, где вы можете вызвать shift, чтобы перейти к следующему аргументу.shift «выталкивает» первый аргумент из массива и возвращает его, изменяя массив в процессе.Обратное значение равно unshift.Полный список:

array.shift()
    [1,2,3] -> [2,3], returns 1
array.unshift(new-element)
    [element, ...] -> [new-element, element, ...]
array.pop()
    [1,2,3] -> [1,2], returns 3
array.push(new-element)
    [..., element] -> [..., element, new-element]

См. Также: ломтик, ломтик

.join("")

Array.prototype.join(string).Эта функция превращает массив в строку.Пример: [1,2,3].join(", ") -> "1, 2, 3"

slide

return setInterval(slideLoop(0, "#container"), 3000);

Прежде всего, мы возвращаем возвращаемое значение setInterval, чтобы оно могло быть использовано позже при вызове clearInterval.Это важно, потому что JavaScript не будет очищать это сам по себе.Я настоятельно не рекомендую использовать setTimeout для создания цикла.Это не то, для чего setTimeout предназначен, и, делая это, вы возвращаетесь к GOTO.Прочтите статью Дейкстры за 1968 год, Перейти к утверждению, которое считается вредным, , чтобы понять, почему циклы GOTO - плохая практика.

Во-вторых, вы заметите, что я сделал некоторые вещи по-другому.Повторяющийся интервал очевиден.Это будет продолжаться вечно, пока интервал не будет очищен, и с задержкой 3000 мс.Значение для callback является возвращаемым значением другой функции, которую я передал аргументам 0 и "#container".Это создаст замыкание, и вы вскоре поймете, как это работает.

slideLoop

function slideLoop(margin, selector)

Мы берем margin (0) и селектор ("#container") в качестве аргументов.Поля - это начальное значение поля, а селектор - это селектор CSS, используемый для поиска изменяемого элемента.Довольно просто.

const multiplier = -600;
let contStyle = document.querySelector(selector).style;

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

Я также создал ссылку на свойство элемента style с помощью селектора CSS. Поскольку style является объектом, это безопасно сделать, так как он будет рассматриваться как ссылка , а не копия (читается на Pass By Sharing чтобы понять эту семантику).

return function(){
    margin = ++margin % 4;
    contStyle.marginLeft = format("%px", margin * multiplier);
}

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

margin = ++margin % 4;
contStyle.marginLeft = format("%px", margin * multiplier);

Здесь мы просто увеличиваем маржу и модулируем ее на 4. Последовательность значений, которые это произведет, равна 1->2->3->0->1->..., которая точно имитирует поведение вопроса без какой-либо сложной или жестко закодированной логики.

Впоследствии мы используем функцию format, определенную ранее, чтобы безболезненно установить CSS-свойство marginLeft контейнера. Он установлен на текущее значение маржи, умноженное на множитель, который, как вы помните, был равен -600. -600 -> -1200 -> -1800 -> 0 -> -600 -> ...


Есть некоторые важные различия между моей версией и версией Эндера, о которой я упоминал в комментарии к их ответу. Я сейчас перейду к рассуждениям:

Используйте document.querySelector(css_selector) вместо document.getElementById(id)

querySelector был добавлен в ES6, если я не ошибаюсь. querySelector (возвращает первый найденный элемент) и querySelectorAll (возвращает список всех найденных элементов) являются частью цепочки прототипов всех элементов DOM (не только document) и принимают селектор CSS, поэтому другие способы найти элемент, кроме как по его идентификатору. Вы можете выполнять поиск по идентификатору (#idname), классу (.classname), отношениям (div.container div div span, p:nth-child(even)) и атрибутам (div[name], a[href=https://google.com]), среди прочего.

Всегда отслеживать возвращаемое значение setInterval(fn, interval), чтобы впоследствии его можно было закрыть с помощью clearInterval(interval_id)

Не стоит оставлять интервал, работающий вечно. Также не очень хорошо писать функцию, которая вызывает себя через setTimeout. Это ничем не отличается от цикла GOTO. Возвращаемое значение setInterval должно быть сохранено и использовано для очистки интервала, когда он больше не нужен. Думайте об этом как о форме управления памятью.

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

Создает, как это

setInterval(function(){
    ...
}, 1000);

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

Array.prototype.forEach в порядке.

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

Вы не хотите смешивать состояния и обратные вызовы вместе, как это сделал Эндер. Это склонно к беспорядку и может быть трудно поддерживать. Состояние должно быть в той же функции, что и анонимная функция, чтобы четко отделить его от остального мира. Лучшее название для slideLoop может быть makeSlideLoop, просто чтобы сделать его более понятным.

Используйте правильные пробелы. Логические блоки, которые делают разные вещи, должны быть разделены одной пустой строкой

Это:

print(some_string);

if(foo && bar)
    baz();

while((some_number = some_fn()) !== SOME_SENTINEL && ++counter < limit)
    ;

quux();

намного легче читать, чем это:

print(some_string);
if(foo&&bar)baz();
while((some_number=some_fn())!==SOME_SENTINEL&&++counter<limit);
quux();

Многие начинающие делают это. Включая маленького 14-летнего меня от 2009 года, и я не избавлялся от этой дурной привычки до, вероятно, 2013 года. Перестаньте пытаться сокрушить ваш код настолько маленьким.

Избегайте "string" + value + "string" + .... Сделайте функцию форматирования или используйте String.prototype.replace(string/regex, new_string)

Опять же, это вопрос читабельности. Это:

format("Hello %! You've visited % times today. Your score is %/% (%%).",
    name, visits, score, maxScore, score/maxScore * 100, "%"
);

намного легче читать, чем это ужасное чудовище:

"Hello " + name + "! You've visited " + visits + "% times today. " + 
"Your score is " + score + "/" + maxScore + " (" + (score/maxScore * 100) +
"%).",

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

visits + "% times today"
          ^ whoops

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

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

Я позаимствовал это правило из лучших практик, связанных с макросами препроцессора C.Но мне не нужно объяснять это;убедитесь сами:

let myValue = someValue < maxValue ? someValue * 2 : 0;
let myValue = (someValue < maxValue) ? (someValue * 2) : (0);

Мне все равно, насколько хорошо вы думаете, что понимаете синтаксис вашего языка, последний ВСЕГДА будет легче читать, чем первый, и удобочитаемость - единственный необходимый аргумент,Вы читаете в тысячи раз больше кода, чем пишете.Не будьте придурком в будущем, только так вы можете похлопать себя по спине, чтобы быть умным в краткосрочной перспективе.

0 голосов
/ 23 ноября 2018

вы можете использовать функцию requestAnimationFrame (), как показано ниже,

function unlimited () {
    requestAnimationFrame(unlimited);
    console.log("arian")
}

unlimited();
0 голосов
/ 29 апреля 2011

попробуйте это:

window.onload = function start() {
    slide();
}
function slide() {
     setInterval("document.getElementById('container').style.marginLeft='-600px'",3000);
     setInterval("document.getElementById('container').style.marginLeft='-1200px'",6000);
     setInterval("document.getElementById('container').style.marginLeft='-1800px'",9000);
     setInterval("document.getElementById('container').style.marginLeft='0px'",12000);
}

setInterval - это, по сути, «бесконечный цикл», и он не затеняет браузер. он ждет нужное время, затем снова идет

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