JavaScript无限循环?

时间:2011-04-29 17:12:12

标签: javascript loops

如何在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锁定,要么只循环一次。我确信这是一件非常简单的事情,即使它必须循环1,000,000次而不是无限次,这对我来说还算合适。

另外,我不想使用jQuery或其他人创建的东西。我正在学习JavaScript,这部分是为了帮助我学习,部分是因为我正在努力制作尽可能多的基于HTML5的系统。

编辑:我认为它冻结的原因是因为它一次执行所有代码,然后只将它存储在缓存或其他东西中。我想要它做的就是经历一次,然后再次从顶部开始,这是我一直认为循环的地方。在“批处理”(命令提示符)脚本中,可以使用“GOTO”命令完成此操作。我不知道JS中是否存在等价物,但这确实是我的目标。

11 个答案:

答案 0 :(得分:63)

正确的方法是使用单个计时器。使用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
}

答案 1 :(得分:19)

您不希望while(true),这会锁定您的系统。

你想要的是一个超时,它自己设置一个超时,如下所示:

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

// boot up the first call
start();

答案 2 :(得分:8)

这是一个很好,整洁的解决方案:(also see the live demo ->

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);
}

既然你正在努力学习,请允许我解释一下它的工作原理。

首先我们声明两个变量:currMargcontStylecurrMarg是一个整数,我们将用它来跟踪/更新容器应该具有的剩余边距。我们在实际更新函数之外(在closure中)声明它,以便它可以不断更新/访问而不会丢失其值。 contStyle只是一个便利变量,它使我们可以访问容器样式,而无需在每个间隔上找到元素。

接下来,我们将使用setInterval来建立一个应该每3秒调用一次的函数,直到我们告诉它停止(这是你的无限循环,而不冻结浏览器)。它的工作原理与setTimeout完全相同,除非它在被取消之前无限发生,而不是只发生一次。

我们将anonymous function传递给setInterval,这将为我们完成工作。第一行是:

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

这是ternary operator。如果currMarg等于0,则会currMarg分配1800的值,否则会currMarg增加600。{/ p >

使用第二行,我们只需将我们选择的值分配给container的marginLeft,我们就完成了!

注意:对于演示,我将负值更改为正值,因此效果可见。

答案 3 :(得分:4)

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);
}

答案 4 :(得分:2)

你连续十次打电话给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)时,不再将设置超时。

答案 5 :(得分:1)

你可以通过递归无限循环。

function it_keeps_going_and_going_and_going() {
  it_keeps_going_and_going_and_going();
}

it_keeps_going_and_going_and_going()

答案 6 :(得分:1)

关键不是要一次安排所有照片,而是每次显示照片时安排下一张照片。

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,它仅安排下一次出现。

答案 7 :(得分:0)

Ender's answer上进行扩展,让我们通过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秒。 Asker希望这44个调用可以一次接一个地执行,但相反,它们都同时执行。
  • 循环结束后3秒,container的marginLeft被设置为"-600px" 11次。
  • 此后3秒钟,marginLeft被设置为"-1200px" 11次。
  • 3秒后,"-1800px",共11次。

以此类推。

您可以将其更改为:

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)));
    }
}

但这只是一个懒惰的解决方案,无法解决此实现中的其他问题。这里有很多硬编码和一般草率的地方应该解决。

从十年的经验中学到的经验教训

正如该答案顶部提到的那样,Ender已经提出了一种解决方案,但我想对其加以补充,以考虑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);
}

让我们研究一下这对所有初学者的工作原理(请注意,并非所有这些都与问题直接相关):

格式

function format

any 语言提供类似printf的字符串格式化程序功能非常有用。我不明白为什么JavaScript似乎没有一个。

format(str, ...args)

...是ES6中新增的一个令人眼花feature乱的功能,可让您做很多事情。我相信它被称为点差算子。语法:...identifier...array。在函数标头中,可以使用它来指定变量参数,它将在该变量参数的位置及之后获取每个参数,并将它们填充到数组中。您还可以使用如下数组调用函数:args = [1, 2, 3]; i_take_3_args(...args),也可以采用类似数组的对象并将其转换为数组:...document.querySelectorAll("div.someclass").forEach(...)。如果没有传播运算符,这将是不可能的,因为querySelectorAll返回一个“元素列表”,它不是一个真正的数组。

str.split(/(%)/)

我不擅长解释正则表达式的工作原理。 JavaScript有两种用于正则表达式的语法。有OO方式(new RegExp("regex", "gi"))和有文字方式(/insert regex here/gi)。我对regex怀有深深的仇恨,因为它所鼓励的简洁语法通常弊大于利(而且还因为它们极不可移植),但是在某些情况下regex会有所帮助,例如这种情况。通常,如果您使用"%"/%/调用split,则结果数组将在数组中排除“%”定界符。但是对于此处使用的算法,我们需要将其包括在内。 /(%)/是我尝试的第一件事,并且有效。我猜很幸运。

.map(...)

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)

?:运算符对是一个三元操作符,称为三元条件运算符。语法:condition ? if-true : if-false,尽管大多数人都将其称为“三元”运算符,因为在每种语言中,它都是唯一的3运算符运算符,其他所有运算符都是二进制(+,&&&,|,=)或一元(++,...,&,*)。有趣的事实:某些语言(以及语言的供应商扩展,例如GNU C)实现了?:运算符的双操作数版本,语法为value ?: fallback,等效于value ? value : fallback,并且将如果fallback的计算结果为false,请使用value。他们称其为猫王操作员。

我还应该提到expressionexpression-statement之间的区别,因为我意识到这可能并不适合所有程序员。 expression代表一个值,可以分配给l-value。表达式可以填充在括号内,而不被视为语法错误。尽管大多数语句都是l-value,但表达式本身可以是r-values,因为仅有的l值表达式是由标识符或引用/指针组成的表达式。函数可以返回l值,但不要指望它。表达式也可以与其他较小的表达式混合。 (1, 2, 3)是由三个r值表达式和两个逗号运算符组成的表达式。该表达式的值为3。expression-statements是由单个表达式形成的语句。 ++somevar是一个表达式,因为它可以用作赋值表达式语句newvar = ++somevar;中的r值(例如,表达式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的镜像版本,表面上是从Shell语言继承的,您可以在其中调用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"

幻灯片

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

首先,我们返回setInterval的返回值,以便稍后可以在对clearInterval的调用中使用它。这很重要,因为JavaScript本身不会清除它。我强烈建议不要使用setTimeout进行循环。这不是setTimeout设计的目的,通过这样做,您将恢复为GOTO。阅读Dijkstra 1968年的论文认为有害的声明,以了解为什么GOTO循环是不好的做法。

第二,您会发现我做了一些不同的事情。重复间隔是显而易见的。这将永久运行,直到清除间隔为止,并且延迟3000毫秒。 回调的值是另一个函数的返回值,我已将其传递给参数0"#container"。这将创建一个闭合,您将很快了解其工作原理。

slideLoop

function slideLoop(margin, selector)

我们以页边距(0)和选择器(“ #container”)作为参数。边距是初始边距值,选择器是用于选择我们要修改的元素的CSS选择器。非常简单。

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

我已经将一些硬编码元素上移了。由于边距是-600的倍数,因此我们有一个明确标记的具有该基值的常数乘数。

我还通过CSS选择器创建了对该元素的 style 属性的引用。由于style是一个对象,因此可以安全地执行此操作,因为它将被视为引用而不是副本(在 Pass上阅读)通过共享以了解这些语义。

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函数轻松设置容器的marginLeft CSS属性。它设置为当前边距值乘以乘数,您记得该乘数被设置为-600。 -600 -> -1200 -> -1800 -> 0 -> -600 -> ...


我的版本与Ender的版本之间存在一些重要区别,我在a comment中提到了他们的答案。我现在要讲究理由:

  

使用document.querySelector(css_selector)代替document.getElementById(id)

如果我没记错的话,在ES6中添加了

querySelector。 querySelector(返回第一个找到的元素)和querySelectorAll(返回所有找到的元素的列表)是 all DOM元素(不仅仅是document)的原型链的一部分,并采用CSS选择器,因此除了通过ID来查找元素外,还有其他方法来查找元素。您可以按ID(#idname),类别(.classname),关系(div.container div div spanp:nth-child(even))和属性(div[name],{{1} })。

  

始终跟踪a[href=https://google.com]的返回值,以便以后可以使用setInterval(fn, interval)将其关闭

让间隔永远运行不是一个好的设计。编写一个通过clearInterval(interval_id)调用自身的函数也不是一个好的设计。这与GOTO循环没有什么不同。应该存储setTimeout的返回值,并在不再需要该间隔时使用它来清除间隔。可以将它视为一种内存管理形式。

  

将时间间隔的回调放入其自己的形式函数中,以提高可读性和可维护性

这样的构造

setInterval

很容易变得笨拙,尤其是当您存储setInterval的返回值时。我强烈建议将函数放在调用之外,并给它起一个名称,以便其清晰并具有自说明性。如果您正在使用闭包(一种特殊类型的对象,它包含函数周围的局部状态),这也使得调用返回匿名函数的函数成为可能。

setInterval(function(){ ... }, 1000); 很好。

  

如果回调函数保持状态,则应从另一个函数(例如Array.prototype.forEach)返回该回调函数以形成闭包

您不想像Ender那样将状态和回调混在一起。这容易造成混乱,并且难以维护。状态应与匿名函数来自的状态相同,以便将其与世界其他地方清楚地分开。 slideLoop的更好称呼可能是slideLoop,只是为了更加清楚。

  

使用适当的空格。做不同事情的逻辑块应该用一个空行分隔

此:

makeSlideLoop

比这更容易阅读:

print(some_string);

if(foo && bar)
    baz();

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

quux();

很多初学者都这样做。包括2009年才14岁的小我,直到大约2013年,我才学会了这个坏习惯。不要再试图将代码压缩得如此之小了。

  

避免使用print(some_string); if(foo&&bar)baz(); while((some_number=some_fn())!==SOME_SENTINEL&&++counter<limit); quux(); 。设置格式功能或使用"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) +
"%).",

这是一个很好的演示,因为我犯了这个错误,并且直到很久都没有注意到这个错误的全部原因是因为代码很难读。

  

始终用括号括住三元表达式的参数。它有助于提高可读性并防止错误。

我从有关C预处理器宏的最佳实践中借鉴了此规则。但是我真的不需要解释这一点。亲自看看:

visits + "% times today"
          ^ whoops

我不在乎您认为您的语言语法有多好,后者总是比前者更容易阅读,并且可读性是唯一必要的参数。您读取的代码比编写的代码多数千倍。不要对自己的未来长久存有任何想法,只是为了在短期内变得聪明而可以轻拍自己。

答案 8 :(得分:0)

这里:

window.onload = function start() {
    slide();
}
function slide() {
    var num = 0;
    for (num=0;num==10;) {
        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);
    }
}

这使得它一直循环正常!这就是为什么它在这里无法运行的原因。

答案 9 :(得分:-1)

试试这个:

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基本上是一个“无限循环”,它不会使浏览器变黑。它等待所需的时间,然后再次

答案 10 :(得分:-1)

您可以像下面那样使用requestAnimationFrame()函数,

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

unlimited();