【问题标题】:How to pause javascript for loop (animating bubble sort)? [duplicate]如何暂停javascript for循环(动画冒泡排序)? [复制]
【发布时间】:2020-09-13 11:05:57
【问题描述】:

我是 javascript 的新手,如果我误解了该语言是如何做一些事情的,很抱歉, 我正在构建一个排序算法可视化器,它根据色调值对块进行排序(使用 chroma-js 库): screenObject.items 中的每一项都是一个 Color 对象

//color objects are what I am sorting
class Color {
  constructor(div, color, value) {
    //this div on the html page
    this.div = div;
    this.color = color;
    //hue value of the color
    this.value = value;
  }
  update(color, value) {
    this.color = color;
    this.value = value;
    this.div.style.backgroundColor = color;
  }
}

class ScreenObject {
  constructor() {
    //this is an array of color objects
    this.items = [];
  }
  bubbleSort() {
    let solved = false;
    while (!solved) {
      let swaps = 0;
      this.items.forEach((item, index) => {
        if (index > 0) {
          swaps += compare(this.items[index - 1], item);
        }
      });
      if (swaps === 0) {
        solved = true;
      }
    }
  }
}

function compare(color1, color2) {
  if (color1.value > color2.value) {
    swap(color1, color2);
    return 1;
  } else {
    return 0;
  }
}

function swap(color1, color2) {
  colorStore = color1.color;
  valueStore = color1.value;
  color1.update(color2.color, color2.value);
  color2.update(colorStore, valueStore);
}

我遇到的问题是,这些颜色仅在程序完成后更新,如果我添加一个 setIterval 或 setTimeout,我只能在每次通过后更新颜色,而不是在每次比较/交换后更新(我想在比较颜色时添加特殊样式):

  bubbleSort() {
    let solved = false;
    while (!solved) {
      let swaps = 0;
      setInterval(() => {
        this.items.forEach((item, index) => {
          if (index > 0) {
            swaps += compare(this.items[index - 1], item);
          }
        });
      }, 50);
      if (swaps === 0) {
        solved = true;
      }
    }
  }

我希望能够在每次比较后看到颜色更新,例如 swap(1, 2) 用户看到 1 得到 2 的颜色和 2 得到 1 的颜色。

提前致谢!

【问题讨论】:

  • 您不能暂停循环。这里的解决方案是首先不使用循环,而只使用递归setTimeout() 或带有计数器的`setInterval()。每次触发计时器回调时,您都会更新计数器,并且您会获得没有实际循环的定时循环的效果。
  • 你可以在forEach循环中添加setInterval
  • @Kenny 这基本上就是 OP 现在正在做的事情,它并没有解决问题,因为间隔回调只会在事件队列中堆积,并且在循环完成之前不会触发,那么所有的回调将一个接一个地触发,它们之间没有间隔。

标签: javascript for-loop animation javascript-objects


【解决方案1】:

我假设您在浏览器上执行此操作。您需要返回到事件循环才能让其他事情发生,例如重新绘制页面。可能最简单的事情是让你的 bubbleSort 成为一个 async 方法并拥有它 await 一些东西,例如计时器回调:

async bubbleSort() { // <=== Note the `async`
  let solved = false;
  while (!solved) {
    let swaps = 0;
    // *** No `setInterval`
    for (const [index, item] of this.items.entries()) {
      if (index > 0) {
        const result = compare(this.items[index - 1], item);
        if (result) {         // *** Did it do something?
          swaps += result;
          await delay(0);     // *** Wait for it to be redrawn
        }
      }
    });
    if (swaps === 0) {
      solved = true;
    }
  }
}

...delay 可能在哪里:

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

当您调用bubbleSort 时,它几乎会立即返回一个promise,并异步继续其逻辑,直到它完成,然后结算promise。

请注意 async 函数是在 ES2018 中添加的。现代浏览器很好地支持它,但您可能需要像 Babel 这样的工具来为旧浏览器转译。


如果您想更精确,您可以使用 requestAnimationFrame 而不是 setTimeout 和零长度超时。这是一个有点违反直觉的函数:

const nextFrame = (cb = () => {}) => new Promise(resolve => {
    requestAnimationFrame(() => {
        cb();
        resolve();
    });
});

...您将使用它来代替 delay:

await nextFrame();

(在你的情况下,你不需要传递任何回调,默认的无操作回调就足够了。)

我在this tweet 中解释了该函数的奇怪设计的原因(这反过来又询问它是否真的需要设计得那么奇怪)。


另一种方法是稍微反转您的逻辑并使用生成每个交换的生成器函数。然后,您可以循环驱动该发电机。这就是我使用的方法when answering this other question about visualizing buble sort

【讨论】:

  • (糟糕!如果您在上面的for (const [/*...*/] of this.items.entries()) 行中看到item, index 而不是index, item,请点击刷新。)
猜你喜欢
  • 1970-01-01
  • 2021-09-26
  • 1970-01-01
  • 2020-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-29
  • 2017-08-16
相关资源
最近更新 更多