我正在编写一个简单的脚本,该脚本一次将一个字符输出到屏幕上。
我正在这样做,以便该函数(我称为slowPrint
)可以接收字符串数组。数组中的每个元素代表一条消息。
这是我到目前为止的代码:
但是,我没有得到预期的输出。 我怀疑这部分是由于代码的异步性质,尽管我对正在发生的事情以及如何解决它没有完全清楚的了解。
首先,<br />
标签是在任何消息之前打印的,这告诉我外循环在嵌套消息甚至开始之前就已经完成了。
但是,当嵌套循环开始时,数组中的每个字符串都间隔一秒钟打印一次,但要完整打印而不是逐字符打印。
我想念什么?
另外,有人可以解释setTimeout
方法的以下行为吗?
方案1 :当我将第二个参数设置为i * 1000
时,第二个字符串打印一个接一个的第二个(同样,整个字符串而不是一个字符一个字符)
const messages = [
"all systems are operational",
"you may proceed"
];
function slowPrint(args) {
let screen = document.getElementById('screen');
for (let i = 0; i < args.length; i++) {
let message = args[i];
for (let j = 0; j < message.length; j++) {
setTimeout(function () {
screen.innerHTML += message[j];
}, i * 1000);
}
screen.innerHTML += '<br />';
}
}
slowPrint(messages)
<div id="screen"></div>
方案2 :当我将第二个参数设置为j * 1000
时,输出完全出乎意料:每隔两个字符以2组为一组打印,但是顺序难以理解;只有最后一个参数的最后一个单词会像其他所有内容一样打印出来。
方案3 :当我将第二个参数设置为1000
时,数组中的所有字符串将在一秒钟后打印出来。
发生了什么事?
答案 0 :(得分:1)
使用async
function和名为sleep()
的帮助程序函数将setTimeout()
包裹在Promise
和await
中,您可以进行最小的更改
const messages = [
'all systems are operational',
'you may proceed'
];
const sleep = ms => new Promise(resolve => { setTimeout(resolve, ms) })
async function slowPrint(args) {
let screen = document.getElementById('screen');
for (let i = 0; i < args.length; i++) {
let message = args[i];
for (let j = 0; j < message.length; j++) {
await sleep(100);
screen.innerHTML += message[j];
}
screen.innerHTML += '<br />';
}
}
slowPrint(messages)
<div id="screen"></div>
setTimeout()
的回调是异步执行的,因此执行顺序将始终如下所示:
// first
setTimeout(function () {
// at *least* after all the current synchronous code has completely finished
})
// second
如评论中所述,async
/ await
仅在实现browsers的ECMAScript 2017中受支持。
答案 1 :(得分:1)
此视频是js在浏览器中的工作方式的最佳解释之一:here
基本上,您放置在setTimeout回调中的任何内容都会以传递给第二个参数的ms数的形式放置在backburner上。然后将其放入回调队列中,直到调用堆栈为空,并且它是队列中的下一个项目
如果将代码复制并粘贴到http://latentflip.com/loupe/中,您将看到它在幕后的实际运行方式
答案 2 :(得分:1)
只需使用setInterval
,即可使用简洁的代码来完成此操作。您只需要适当地管理索引。此代码使用i
遍历每个字母,并使用j
遍历数组。当i
达到限制时,j
会增加;当j
达到限制时,间隔将被清除。
let screen = document.getElementById('screen');
const messages = [
"all systems are operational",
"you may proceed"
];
function slowPrint(args) {
let i=0, j = 0
let ivl = setInterval(() => {
screen.innerHTML += args[j][i]
i++
if (i == args[j].length ){
i = 0;
j++
screen.innerHTML += '<br>'
}
if (j === args.length) clearInterval(ivl)
}, 200)
}
slowPrint(messages)
<div id="screen"></div>
您的代码出现问题的原因是for循环不会停止并等待超时。 for循环使全部超时几乎同时开始,因此1000毫秒后它们全部触发。当您需要定期进行某些操作时,setInterval
通常是一种更好的方法。
当然,还有许多种其他方式可以做到这一点。只是一些更奇特的示例,这是一种使用简单生成器的方法。有点难以理解,但是一旦习惯了发电机,看起来就会很干净:
const out = document.getElementById('screen')
const messages = ["all systems are operational","you may proceed"];
function *iter(messages) {
for(m of messages){
for(letter of m) yield letter
yield '<br>'
}
}
const gen = iter(messages)
const int = setInterval(() => {
let n = gen.next()
if (n.done) return clearInterval(int)
out.innerHTML += n.value
}, 100)
<div id='screen'></div>
答案 3 :(得分:0)
从使用队列到使用数学计算范围,有无数种方法可以做到这一点。无需过多修改代码,您只需检查您是否在最后一个字符处,然后附加一个换行符并使用一个变量来跟踪输出的当前时间。
const messages = [
"all systems are operational",
"you may proceed"
];
function slowPrint(args) {
let screen = document.getElementById('screen');
let delay = 0;
const timeDelay = 100;
for (let i = 0; i < args.length; i++) {
let message = args[i];
for (let j = 0; j < message.length; j++) {
setTimeout(function () {
let lineBr = j === message.length - 1 ? '<br>' : ''
screen.innerHTML += message[j] + lineBr;
}, delay);
delay += timeDelay
}
}
}
slowPrint(messages)
<div id="screen"></div>
我个人会使用更多的队列,这样就不会创建大量的计时器。
const messages = [
"all systems are operational",
"you may proceed"
];
function slowPrint(args) {
let screen = document.getElementById('screen');
// combine all the strings into one character array
var characters = messages.reduce( function (a, s) {
// turn string into an array of characters
var letters = s.split('')
// last character, add a line break
var l = letters.length-1
letters[l] = letters[l] + '<br/>'
// append it to our current list
return a.concat(letters);
}, []);
function next() {
// append the first character of the array to our output
screen.innerHTML += characters.shift()
// if we still have more characters, than run it again
if (characters.length) window.setTimeout(next, 100);
}
// kick off the script to output the characters
next()
}
slowPrint(messages)
<div id="screen"></div>