JavaScript 的异步编程特性,既是福音,也可能是诅咒,因为它会导致“回调地狱”。虽然 Async.js 等实用程序库可以帮助组织异步代码,但有效跟踪控制流并推断异步代码的逻辑仍然很困难。
本文将介绍响应式编程的概念,它利用 Bacon.js 等库来处理 JavaScript 的异步特性。
关键要点
开始响应式编程
响应式编程处理的是异步数据流。它用可观察模式取代了迭代器模式。这与命令式编程不同,在命令式编程中,您主动迭代数据来处理事务。在响应式编程中,您订阅数据并异步响应事件。
Bart De Smet 在他的演讲中解释了这种转变。André Staltz 在这篇文章中深入探讨了响应式编程。
一旦您开始使用响应式编程,一切都会变成异步数据流:服务器上的数据库、鼠标事件、Promise 和服务器请求。这使您可以避免所谓的“回调地狱”,并提供更好的错误处理。这种方法的另一个强大功能是能够组合数据流,这为您提供了极大的控制和灵活性。Jafar Husain 在他的演讲中解释了这些概念。
Bacon.js 是一个响应式编程库,它是 RxJS 的替代方案。在接下来的部分中,我们将使用 Bacon.js 来构建一个广为人知的“吃豆人”游戏的版本。
项目设置
要安装 Bacon.js,您可以使用 Bower,在 CLI 上运行以下命令:
<code class="language-bash">$ bower install bacon</code>
安装库后,您就可以开始使用响应式编程了。
为了外观和感觉,我将使用基于文本的系统,这样我就不必处理资源和精灵。为了避免自己创建,我将使用一个很棒的库 UnicodeTiles.js。
首先,我创建了一个名为 PacmanGame 的类,它处理游戏逻辑。它提供以下方法:
此外,它还公开了以下回调:
因此,要使用此 API,我们将启动游戏,定期调用 spawnGhost 来生成幽灵,侦听 onPacmanMove 回调,并且每当发生这种情况时,调用 movePacman 来实际移动吃豆人。我们还定期调用 updateGhosts 来更新幽灵的移动。最后,我们定期调用 tick 来更新更改。重要的是,我们将使用 Bacon.js 来帮助我们处理事件。
在开始之前,让我们创建游戏对象:
<code class="language-bash">$ bower install bacon</code>
我们创建一个新的 PacmanGame,并传入一个父 DOM 对象 parentDiv,游戏将在此对象中渲染。现在我们准备构建我们的游戏了。
事件流或可观察对象
事件流 是一个可观察对象,您可以订阅它以异步观察事件。您可以使用以下三种方法观察这三种类型的事件:
既然我们已经了解了事件流的基本用法,那么让我们看看如何创建一个事件流。Bacon.js 提供了几种方法,您可以使用这些方法从 jQuery 事件、Ajax Promise、DOM EventTarget、简单的回调甚至数组创建事件流。
关于事件流的另一个有用的概念是时间概念。也就是说,事件可能会在未来的某个时间到来。例如,这些方法创建在某个时间间隔内传递事件的事件流:
为了获得更多控制,您可以使用 Bacon.fromBinder() 来创建自己的事件流。我们将在游戏中通过创建一个 moveStream 变量来展示这一点,该变量将为我们的吃豆人移动生成事件。
<code class="language-javascript">var game = new PacmanGame(parentDiv);</code>
我们可以使用一个值来调用 sink,该值将发送一个事件,观察者可以侦听该事件。对 sink 的调用在我们 onPacmanMove 回调中——也就是说,每当用户按下按键请求吃豆人移动时。因此,我们创建了一个可观察对象,它发出关于吃豆人移动请求的事件。
请注意,我们使用一个简单值 moveV 调用了 sink。这将使用值 moveV 推送移动事件。我们还可以推送诸如 Bacon.Error 或 Bacon.End 之类的事件。
让我们创建另一个事件流。这次我们想要发出通知以生成幽灵的事件。我们将为此创建一个 spawnStream 变量:
<code class="language-bash">$ bower install bacon</code>
Bacon.sequentially() 创建一个流,该流以给定的间隔传递值。在我们的例子中,它将每 800 毫秒传递一个幽灵颜色。我们还有一个对 delay() 方法的调用。它延迟了流,因此事件将在 2.5 秒的延迟后开始发出。
在本节中,我将列出一些更实用的方法,这些方法可用于事件流:
有关事件流的更多方法,请参阅官方文档页面。可以使用大理石图查看节流和去抖动之间的区别:
<code class="language-javascript">var game = new PacmanGame(parentDiv);</code>
如您所见,节流按惯例节流事件,而去抖动仅在给定的“静默期”后才发出事件。
这些实用程序方法简单而强大,能够概念化和控制流,从而控制其中的数据。我建议观看这段关于 Netflix 如何使用这些简单方法创建自动完成框的演讲。
到目前为止,我们已经创建和操作了事件流,现在我们将通过订阅流来观察事件。
回顾一下我们之前创建的 moveStream 和 spawnStream。现在让我们订阅它们:
<code class="language-javascript">var moveStream = Bacon.fromBinder(function(sink) { game.onPacmanMove = function(moveV) { sink(moveV); }; });</code>
尽管您可以使用 stream.subscribe() 来订阅流,但您也可以使用 stream.onValue()。不同之处在于,subscribe 将发出我们之前看到的这三种类型的事件,而 onValue 仅发出 Bacon.Next 类型的事件。也就是说,它将忽略 Bacon.Error 和 Bacon.End 事件。
当 spawnStream 上出现事件时(每 800 毫秒发生一次),其值将是幽灵颜色之一,我们使用该颜色来生成幽灵。当 moveStream 上出现事件时,请记住,当用户按下按键来移动吃豆人时,就会发生这种情况。我们使用方向 moveV 调用 game.movePacman:它随事件一起出现,因此吃豆人会移动。
您可以组合事件流以创建其他流。组合事件流的方法有很多,这里列出其中几种:
让我们来看一个 Bacon.combineTemplate 的例子:
<code class="language-bash">$ bower install bacon</code>
如您所见,我们使用模板将事件流(即 password、username、firstname 和 lastname)组合成一个名为 loginInfo 的组合事件流。每当事件流接收到事件时,loginInfo 流都会发出事件,将所有其他模板组合成单个模板对象。
Bacon.js 还有一种组合流的方法,即 Bacon.Bus()。Bacon.Bus() 是一个事件流,允许您将值推入流中。它还允许将其他流插入 Bus 中。我们将使用它来构建游戏的最后一部分:
<code class="language-javascript">var game = new PacmanGame(parentDiv);</code>
现在我们使用 Bacon.interval 创建另一个流——ghostStream。此流将每 1 秒发出 0。这次我们订阅它并调用 game.updateGhosts 来移动幽灵。这是为了每 1 秒移动一次幽灵。请注意已注释掉 game.tick,并记住 moveStream 中的其他 game.tick?这两个流都会更新游戏,最后调用 game.tick 来渲染更改,因此,与其在每个流中调用 game.tick,我们可以生成第三个流——这两个流的组合——并在组合流中调用 game.tick。
为了组合流,我们可以使用 Bacon.Bus。这是我们游戏中最终的事件流,我们称之为 combinedTickStream。然后我们将 moveStream 和 ghostStream 插入其中,最后订阅它并在其中调用 game.tick。
就是这样,我们完成了。唯一剩下的就是使用 game.start(); 启动游戏。
Bacon.Property 和更多示例
Bacon.Property 是一种响应式属性。想象一下,一个响应式属性是一个数组的总和。当我们向数组添加一个元素时,响应式属性将做出反应并自行更新。要使用 Bacon.Property,您可以订阅它并侦听更改,或者使用 property.assign(obj, method) 方法,该方法在属性更改时调用给定对象的方法。这是一个如何使用 Bacon.Property 的示例:
<code class="language-javascript">var moveStream = Bacon.fromBinder(function(sink) { game.onPacmanMove = function(moveV) { sink(moveV); }; });</code>
首先,我们创建一个事件流,该流以 1 秒的间隔生成给定数组的值——1、2、3 和 4,然后我们创建一个响应式属性,它是扫描结果。这将为 reactiveValue 分配 1、3、6 和 10 的值。
了解更多信息和实时演示
在本文中,我们通过构建吃豆人游戏介绍了使用 Bacon.js 进行响应式编程。它简化了我们的游戏设计,并通过事件流的概念为我们提供了更多控制和灵活性。完整的源代码可在 GitHub 上找到,实时演示可在此处找到。
以下是一些更有用的链接:
要开始使用 Bacon.js 构建自己的吃豆人游戏,您首先需要了解 JavaScript 和函数式响应式编程 (FRP) 的基础知识。掌握这些知识后,您可以开始设置开发环境。您需要在计算机上安装 Node.js 和 npm(Node 包管理器)。之后,您可以使用 npm 安装 Bacon.js。设置好所有内容后,您可以开始编写游戏代码。您可以按照我们网站上的教程获取有关如何使用 Bacon.js 构建吃豆人游戏的逐步指南。
Bacon.js 是一个用于 JavaScript 的函数式响应式编程 (FRP) 库。它允许您以更易于管理和阅读的方式处理异步事件,例如用户输入。在构建吃豆人游戏的过程中,Bacon.js 可用于处理用户输入(例如键盘事件)、游戏逻辑(例如吃豆人和幽灵的移动)以及将游戏状态渲染到屏幕上。
当然可以!使用 Bacon.js 构建基本吃豆人游戏后,您可以根据自己的喜好进行自定义。您可以更改游戏的视觉效果、添加新功能,甚至修改游戏的规则。可能性是无限的,最好的部分是,您可以在仍然受益于 Bacon.js 和函数式响应式编程的强大功能和简单性的同时做到这一切。
调试使用 Bacon.js 构建的吃豆人游戏类似于调试任何其他 JavaScript 应用程序。您可以使用浏览器的开发者工具来检查代码、设置断点和单步执行代码。此外,Bacon.js 提供了一种名为“onError”的方法,您可以使用它来处理事件流中的错误。
有几种方法可以优化使用 Bacon.js 构建的吃豆人游戏的性能。一种方法是最小化 DOM 更新的数量。您可以使用 Bacon.js 的“combineTemplate”函数将多个流组合成一个更新 DOM 的单个流来实现此目的。另一种方法是使用“flatMap”函数来避免创建不必要的流。
是的,您可以使用 Bacon.js 构建任何需要处理异步事件的游戏类型。这不仅包括像吃豆人这样的经典街机游戏,还包括更复杂的游戏,例如实时战略游戏或多人在线游戏。
向使用 Bacon.js 构建的吃豆人游戏中添加多人游戏功能需要一个服务器来处理玩家之间的通信。您可以为此使用 Node.js 和 WebSockets。在客户端,您将使用 Bacon.js 来处理传入和传出的 WebSocket 消息。
是的,您可以将使用 Bacon.js 构建的吃豆人游戏部署到网站上。您需要使用 Webpack 或 Browserify 等工具捆绑您的 JavaScript 代码,然后您可以将捆绑的代码和游戏的资源(例如图像和声音)托管在 Web 服务器上。
是的,您可以将 Bacon.js 与其他 JavaScript 库或框架一起使用。Bacon.js 是一个独立的库,因此它不依赖于其他库或框架。但是,它可以与其他库或框架结合使用以构建更复杂的应用程序。
网上有很多资源可以学习函数式响应式编程 (FRP) 和 Bacon.js。您可以从官方 Bacon.js 文档开始,该文档提供了对该库的功能和 API 的全面指南。还有许多教程、博客文章和在线课程更详细地介绍了 FRP 和 Bacon.js。
以上是用Bacon.js建立Pacman游戏的详细内容。更多信息请关注PHP中文网其他相关文章!