如何在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中是否存在等价物,但这确实是我的目标。
答案 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);
}
既然你正在努力学习,请允许我解释一下它的工作原理。
首先我们声明两个变量:currMarg
和contStyle
。 currMarg
是一个整数,我们将用它来跟踪/更新容器应该具有的剩余边距。我们在实际更新函数之外(在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);
}
}
在现实中发生的是……
container
的marginLeft被设置为"-600px"
11次。"-1200px"
11次。"-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
。他们称其为猫王操作员。
我还应该提到expression
和expression-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"
。这将创建一个闭合,您将很快了解其工作原理。
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中提到了他们的答案。我现在要讲究理由:
如果我没记错的话,在ES6中添加了使用
document.querySelector(css_selector)
代替document.getElementById(id)
querySelector。 querySelector(返回第一个找到的元素)和querySelectorAll(返回所有找到的元素的列表)是 all DOM元素(不仅仅是document
)的原型链的一部分,并采用CSS选择器,因此除了通过ID来查找元素外,还有其他方法来查找元素。您可以按ID(#idname
),类别(.classname
),关系(div.container div div span
,p: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();