处理 JavaScript 幻灯片中的递归问题
使用 JavaScript 构建无休止的幻灯片时,一个常见的挑战是处理函数调用中的递归。当函数重复调用自身时,就会发生递归,这可能导致无限循环和不断增长的调用堆栈。如果幻灯片函数使用 Promises 进行异步操作(例如获取图像),这尤其成问题。
在这种情况下,虽然代码可以正常运行,但存在递归会使浏览器的调用堆栈过载的风险,从而导致性能问题。 JavaScript 的调用堆栈不是无限的,因此重复的递归调用最终会导致浏览器因内存使用过多而崩溃或锁定。
尝试用递归函数替换 而(真) 循环是一个诱人的解决方案,但这种方法可能会因消耗过多的 CPU 资源而冻结浏览器。因此,使用谨慎的方法来控制幻灯片的流程 承诺 对于确保性能和稳定性至关重要。
本文探讨如何通过将递归逻辑转换为受控循环结构来避免 JavaScript 函数中的递归。我们将演练幻灯片函数的真实示例,确定递归可能出现问题的位置,并演示如何在不锁定浏览器的情况下解决问题。
修改递归 JavaScript 函数以避免调用堆栈溢出
JavaScript - 基于 Promise 的方法,带有间隔循环以避免递归
const duration = 2000; // Time to display each slide in milliseconds
const sizes = [[4000, 500], [1000, 4000], [600, 400], [100, 200], [4000, 4000]];
let n = 0;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
function showSlides(duration) {
const myParent = document.querySelector('#slide-div');
setInterval(async () => {
let sizeIndex = n++ % sizes.length;
let w = sizes[sizeIndex][0];
let h = sizes[sizeIndex][1];
let myRandomizer = `https://placehold.co/${w}x${h}?text=${w}x${h}`;
try {
let myResponse = await fetch(myRandomizer);
let myBlob = await myResponse.blob();
let myUrl = URL.createObjectURL(myBlob);
URL.revokeObjectURL(myParent.querySelector('img').src);
myParent.querySelector('img').src = myUrl;
} catch (error) {
console.error('Error: ', error);
}
}, duration);
}
使用不带递归的异步 JavaScript
JavaScript - 使用带有 Promises 的循环并避免 setInterval 的解决方案
const duration = 2000; // Time to display each slide in milliseconds
const sizes = [[4000, 500], [1000, 4000], [600, 400], [100, 200], [4000, 4000]];
let n = 0;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function showSlides(duration) {
const myParent = document.querySelector('#slide-div');
while (true) {
let sizeIndex = n++ % sizes.length;
let w = sizes[sizeIndex][0];
let h = sizes[sizeIndex][1];
let myRandomizer = `https://placehold.co/${w}x${h}?text=${w}x${h}`;
try {
let myResponse = await fetch(myRandomizer);
let myBlob = await myResponse.blob();
let myUrl = URL.createObjectURL(myBlob);
URL.revokeObjectURL(myParent.querySelector('img').src);
myParent.querySelector('img').src = myUrl;
} catch (error) {
console.error('Error: ', error);
}
await sleep(duration);
}
}
使用事件驱动方法避免递归
解决 JavaScript 幻灯片中的递归问题的另一个重要方面是探索事件驱动的方法。而不是依赖像这样的计时器 设置时间间隔 或者递归调用,事件驱动编程允许脚本动态响应事件。例如,幻灯片放映可以等待用户交互,例如“下一个”或“上一个”按钮,或特定的按键事件,而不是以固定的时间间隔自动播放幻灯片。这将执行控制权转移给用户,减少不必要的 CPU 使用,同时仍保持响应能力。
此外,使用 请求动画帧 在需要幻灯片之间平滑过渡的情况下,该方法还可以帮助消除递归。不像 设置时间间隔,定期运行代码, 请求动画帧 将幻灯片的更新与屏幕的刷新率同步,创建更流畅的动画。它还具有在浏览器选项卡处于非活动状态时暂停的优点,从而减少不必要的计算。这对于提高性能和处理动画而不阻塞调用堆栈特别有用。
另一个关键优化是利用浏览器的内置事件循环和微任务队列。通过将幻灯片进度附加到特定的浏览器事件,例如当上一张图像完全加载时或当用户滚动到某个点时,幻灯片可以无缝集成到用户体验中,而不会出现性能问题。这避免了连续函数调用的需要,并确保每个转换都得到有效且异步的处理。
关于避免 JavaScript 幻灯片中递归的常见问题
- JavaScript 中的递归是什么?为什么它在幻灯片中是一个问题?
- 当函数调用自身时就会发生递归,如果连续这样做,可能会导致堆栈溢出。在幻灯片放映中,这会导致内存使用过多,并可能导致浏览器崩溃。
- 如何避免 JavaScript 函数中的递归?
- 一种解决方案是使用 setInterval 或者 setTimeout 无需递归即可安排任务。另一种选择是事件驱动模型,其中功能由特定用户或浏览器事件触发。
- 为什么我尝试使用 while(true) 锁定浏览器?
- 使用 while(true) 没有像这样的异步操作 await 或者 setTimeout 连续循环运行而不暂停,这会阻塞主线程,导致浏览器冻结。
- 我可以使用吗 Promises 以避免递归?
- 是的, Promises 允许异步执行而无需递归函数调用。这可确保每个操作在下一个操作开始之前完成,从而防止堆栈溢出。
- 什么是 requestAnimationFrame 它有什么帮助?
- requestAnimationFrame 是一种允许您创建与浏览器刷新率同步的平滑动画的方法。它非常高效,并且可以在浏览器选项卡处于非活动状态时防止不必要的计算。
避免连续循环的递归
避免 JavaScript 函数中的递归,特别是在使用时 承诺,对于维持性能至关重要。通过切换到基于循环的方法或事件驱动模型,开发人员可以防止调用堆栈无休止地增长并避免浏览器崩溃。
使用类似的方法 设置时间间隔 或者 请求动画帧以及有效处理异步操作,将允许顺利执行幻灯片等任务。这些解决方案提供更好的内存管理并防止与递归函数调用相关的问题,确保长时间运行的进程的稳定性。
JavaScript 幻灯片优化的来源和参考
- 有关 JavaScript 中的递归和处理调用堆栈的信息可以在以下位置找到: MDN 网络文档:JavaScript 递归 。
- 为了更好地理解 JavaScript 中 Promise 的使用,请参考 JavaScript.info:Promise 基础知识 。
- 有关性能的更多详细信息 设置时间间隔 和 请求动画帧 可以在 MDN 文档中找到。
- 有关创建动态图像对象的指导 创建对象URL 和 撤销对象URL ,访问 MDN 的 URL API 部分。
- 有关 JavaScript 中异步操作的更多信息,请参见 freeCodeCamp:异步编程和回调 。