使用 JavaScript ES6 生成器可视化流程
什么是生成函数?
为什么要可视化函数的运行过程呢?
让我们来想象一下!
人们投入无数时间设计那些只需几分之一秒就能运行的函数。当函数执行如此迅速时,其精妙的实现方式往往难以被充分欣赏。让我们放慢速度,花些时间仔细观察它们的运行过程。
在本文中,我将介绍生成器函数的基础知识以及如何使用它们来可视化函数的执行过程。
什么是生成函数?
生成器函数是 JavaScript 中的新特性,许多人一直在努力寻找它们在实际应用中的用途。我很高兴向大家展示一种很棒的用法,但我们先来回顾一下基础知识。下面是一个简单的生成器函数:
function * myGeneratorFunction(arg) {
yield 1;
yield arg;
}
它看起来很像一个普通的函数,只是有两个不同之处:一个是后面的星号(*)function,另一个是使用了yield语句。
以下是 myGeneratorFunction 的使用方法:
const generator = myGeneratorFunction('hello world');
console.log(generator.next().value)
// logs out 1
console.log(generator.next().value)
// logs out 'hello world'
调用生成器函数并不会立即执行,而是返回一个生成器对象。调用.next()该生成器对象会使 `myGeneratorFunction` 执行到第一个 `yield` 语句为止,并返回 `yield` 关键字之后的值。生成器允许我们停止和启动函数的执行。更多信息请参阅 MDN 上的生成器页面。
为什么要可视化函数的运行过程呢?
将函数的运行过程可视化有助于理解其实现方式,并且可以产生引人入胜的动画和令人印象深刻的效果。例如,这段视频就可视化了各种排序算法:
这段视频说明了流程可视化为何如此出色:
- 观看分类过程有一种奇特的吸引力。
- 每种排序算法的工作原理差异显而易见。
- 要想激发人们对某事物运作方式的兴趣,还有什么比让其运作方式看起来很酷更好的方法呢?
让我们来想象一下!
如今的计算机运行速度快得惊人,比博尔特还快,快得令人难以置信。这意味着函数的运行速度也同样惊人。借助生成器,我们可以减慢函数的运行速度,使其以每秒 60 步的速度运行。在这个速度下,我们可以实时观察函数如何发挥其最佳性能。这就像慢动作观看世界上最快的短跑运动员,观察他们在每一步中肌肉的收缩和放松。
为了演示,我们不妨直接复制上面的 YouTube 视频,用条形图来可视化插入排序算法。下面是我们需要用到的两段代码:一段用于实现基本算法,另一段用于绘制条形图。接下来,我们将介绍如何轻松地将它们组合起来。
这是插入排序算法的基本实现:
function insertionSort(inputArray) {
for (let i = 0; i < inputArray.length; i++) {
const value = inputArray[i];
let j = i - 1;
while (j >= 0 && value < inputArray[j]) {
inputArray[j+1] = inputArray[j];
j -= 1;
}
inputArray[j+1] = value
}
return inputArray;
}
下面这个函数可以将数组绘制成条形图并显示在画布上。我使用的是 2D Canvas API:
const c = document.getElementById('canvasEl');
const ctx = c.getContext('2d');
function drawGraphFromArray(array) {
ctx.clearRect(0,0,c.width,c.height);
const barWidth = c.width / array.length;
const barHeightScale = c.height / Math.max(...array);
array.forEach((value, i) => ctx.fillRect(
i * barWidth,
0,
barWidth,
barHeightScale * value
));
}
现在回到我们常规的编程。为了降低插入排序函数的运行速度,我们将把它重写成生成器函数。听起来很棘手,对吧?其实恰恰相反,它超级简单。以下是重写的插入排序函数:
function * insertionSort(inputArray) {
for (let i = 0; i < inputArray.length; i++) {
const value = inputArray[i];
let j = i - 1;
while (j >= 0 && value < inputArray[j]) {
inputArray[j+1] = inputArray[j];
j -= 1;
yield inputArray;
}
inputArray[j+1] = value
}
return inputArray;
}
仅做了两处改动。我们*在函数关键字后添加了一个 `\r`,并yield在每次需要绘制动画帧时添加一个语句,以确保数组已排序。通过这些简单的改动,我们将一个函数转换为一个生成器函数,该函数一次执行一个步骤,并生成我们可视化其过程所需的数据。这种重写方式非常出色,因为它几乎没有影响原有代码——转换几乎不会影响函数的逻辑。
现在让我们把drawGraphFromArray新的insertionSort生成器函数放到requestAnimationFrame渲染循环中。
// code from above ...
const randomArr = Array(50).fill(0).map(Math.random);
const sortGenerator = insertionSort(randomArr);
function renderLoop() {
const yieldedArray = sortGenerator.next().value;
drawGraphFromArray(yieldedArray);
requestAnimationFrame(renderLoop);
}
renderLoop();
最终形成了我们完成的动画:
在上面的最终动画中,我们可以看到柱状图从锯齿状的混乱状态变成了优美的阶梯状。为了实现这一点,我们让插入排序算法在每个渲染循环中执行一步操作.next()。requestAnimationFrame这样可以确保渲染循环以每秒 60 帧的速度运行,这是流畅动画的理想速度。
插入排序是我们能够做到的事情的一个简单例子……
本文最初发表于我的博客 http://elliot.website/a/?Visualizing%20Process%20with%20ES6%20Generators。欢迎访问该博客,查看更多关于使用生成器进行创意编程的额外内容。
感谢阅读。你有没有创造性地使用过生成器函数?
文章来源:https://dev.to/elliot/visualizing-process-with-javascript-es6-generators-31fo