如何让JavaScript按顺序遍历一组预定义的响应?
我想创建一个设置,让用户可以和一个聊天机器人对话,机器人会按顺序依次遍历一个预设的回答列表。机器人每次接收到用户输入时都会回应,但输入值并不决定回应内容。本质上,每次用户点击“发送”按钮,机器人应该给出列表中的下一个单词。
当前机器人每次都回应列表中的最后一项。我认为需要解决的问题基本是:
-
让函数能够连续遍历列表,并跟踪上一次使用的是哪一项
-
让函数在用户点击提交时生成一个新的单词,而无需重新加载页面(这是我在过去创建随机单词生成器时也遇到的问题)
以下是我的代码,我希望机器人按顺序一次给出一个水果列表中的项。
<div class="chatbox">
<div class="chat-window" id="chat-window">
<!-- Messages displayed here -->
</div>
<div class="chat-input">
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-button">Send</button>
</div>
</div>
document.getElementById('send-button').addEventListener('click', sendMessage);
document.getElementById('message-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
sendMessage();
}
});
function sendMessage() {
var input = document.getElementById('message-input');
var message = input.value.trim();
if (message === '') return;
appendMessage('user', message);
input.value = '';
setTimeout(() => {
appendMessage('bot', botMessage);
}, 1000);
}
var words = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew"]
for (var index = 0, len = words.length; index < len; ++index) {
var botMessage = words[index];
console.log(botMessage);
}
function appendMessage(sender, message) {
var chatWindow = document.getElementById('chat-window');
var messageElement = document.createElement('div');
messageElement.classList.add('message', sender);
messageElement.textContent = message;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
解决方案
你说:
当前机器人每次都回应列表中的最后一个项。我认为需要解决的问题基本是:
- 让函数能够连续遍历列表,并跟踪上一次使用的是哪一项
- 在用户点击提交时运行函数以生成一个新的单词,而无需重新加载页面(这是我在过去创建随机单词生成器时也遇到的问题)
由于你没有展示与第2点相关的任何代码(因为它实际上是一个完全独立的问题,应该放在一个单独的提问中),所以我将忽略它,专注于第1点。
在函数 sendMessage 中,你使用定时器来创建「机器人回应」:
setTimeout(() => {
appendMessage("bot", botMessage);
}, 1000);
这个 botMessage 是对外部作用域中的一个变量的引用。它是通过遍历数组 words 来创建的:
var words = [
"apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape",
"honeydew",
];
for (var index = 0, len = words.length; index < len; ++index) {
var botMessage = words[index];
console.log(botMessage);
}
循环每次迭代时,botMessage 的值都会被更新(被覆盖),因此当循环执行到最后一次时,botMessage 的值等于数组中的最后一个元素("honeydew")。因此,每次创建「机器人回应」时,值总是 "honeydew"。
这与你的观察结果一致:
当前机器人每次都回应列表中的最后一项。
为了修复这个问题,你需要在列表中跟踪“当前的位置”,以便每次需要创建机器人消息时,可以使用数组中的相应单词,而不是始终引用一个静态变量。
因为数组是按整数索引的,你可以只需要跟踪索引并在每次增加——当你到达数组的最后一个索引时,将其设回 0 以实现循环。你可以这样写,使用取模运算符 %(链接保留原样):
const words = [
"apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape",
"honeydew",
];
let wordIndex = 0;
function getNextWord() {
const word = words[wordIndex];
wordIndex = (wordIndex + 1) % words.length;
return word;
}
然后,在函数 sendMessage 中,你可以在 setTimeout 之前像这样初始化 botMessage:
const botMessage = getNextWord();
setTimeout(() => appendMessage("bot", botMessage), 1000);
如果这是你反复遇到的场景,你可以创建一个在数组上循环的抽象,使用一个实现了 迭代协议 的 类,来维护索引状态——例如:
class CyclingIterator {
#index;
constructor(array) {
this.array = [...array];
this.#index = 0;
}
[Symbol.iterator]() {
return this;
}
next() {
const value = this.array[this.#index];
this.#index = (this.#index + 1) % this.array.length;
return { value };
}
}
const cycleWords = new CyclingIterator([
"apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape",
"honeydew",
]);
……并在函数 sendMessage 中,你可以通过从迭代器中获取下一个值来创建 botMessage:
const botMessage = cycleWords.next().value;
setTimeout(() => appendMessage("bot", botMessage), 1000);
下面是一个包含你问题中的代码以及上述修改的代码片段:
document.getElementById('send-button').addEventListener('click', sendMessage);
document.getElementById('message-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
sendMessage();
}
});
class CyclingIterator {
#index;
constructor(array) {
this.array = [...array];
this.#index = 0;
}
[Symbol.iterator]() {
return this;
}
next() {
const value = this.array[this.#index];
this.#index = (this.#index + 1) % this.array.length;
return { value };
}
}
const cycleWords = new CyclingIterator([
"apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape",
"honeydew",
]);
function sendMessage() {
var input = document.getElementById("message-input");
var message = input.value.trim();
if (message === "") return;
appendMessage("user", message);
input.value = "";
const botMessage = cycleWords.next().value;
setTimeout(() => appendMessage("bot", botMessage), 1000);
}
function appendMessage(sender, message) {
var chatWindow = document.getElementById('chat-window');
var messageElement = document.createElement('div');
messageElement.classList.add('message', sender);
messageElement.textContent = message;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
<div class="chatbox">
<div class="chat-window" id="chat-window">
<!-- Messages displayed here -->
</div>
<div class="chat-input">
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-button">Send</button>
</div>
</div>
备选方案
- 捕捉输入和回车键的正确方式,是使用一个表单并阻止
submit事件。 - 你需要一个全局变量来记住上次使用的单词。
Notes:
- 你的机器人回应与消息并不完全同步。例如,用户可能在收到意外答案前提问两次。我已经通过使用IIFE修复了这一点。
- 如果你的页面重新加载,你仍然可以在开发者工具中持久化日志时看到控制台中的错误。
var currentIndex = 0;
var words = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew"]
document.getElementById('frm').addEventListener('submit', function(ev) {
ev.preventDefault();
sendMessage()
});
function sendMessage() {
var input = document.getElementById('message-input');
var message = input.value.trim();
if (message === '') return;
appendMessage('user', message);
input.value = '';
input.focus();
botMessage = words[currentIndex];
currentIndex = (currentIndex + 1) % words.length;
// copy the botMessage into a function that would remember it
(function(botMessage) {
setTimeout(() => {
appendMessage('bot', botMessage);
}, 1000);
})(botMessage);
}
function appendMessage(sender, message) {
var chatWindow = document.getElementById('chat-window');
var messageElement = document.createElement('div');
messageElement.classList.add('message', sender);
messageElement.textContent = message;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
.bot {
color: green;
}
.user {
color: blue;
}
<div class="chatbox">
<div class="chat-window" id="chat-window">
<!-- Messages displayed here -->
</div>
<div class="chat-input">
<form id="frm">
<input type="text" id="message-input" placeholder="Type a message...">
<button>Send</button>
</form>
</div>
</div>