各位靓仔靓女,晚上好!我是你们今晚的JS状态机特约讲师,老司机带你用Generator飙车!
今天咱们聊聊一个听起来高大上,但其实玩起来贼有意思的东西:用JS的Generator实现状态机。保证让你听完之后,感觉自己一下子从青铜跳到王者,代码写得飞起!
啥是状态机?
简单来说,状态机就是描述一个对象在不同状态之间如何转换的模型。 想象一下红绿灯,它有三种状态:红灯、黄灯、绿灯。 状态之间有明确的转换规则,比如绿灯变黄灯,黄灯变红灯,红灯变绿灯。
状态机这玩意儿,在很多地方都有用武之地:
- 游戏开发: 角色状态(待机、行走、攻击、死亡),AI行为等等。
- UI开发: 组件状态(显示、隐藏、加载中、错误)。
- 工作流引擎: 任务状态(待处理、处理中、已完成、已拒绝)。
- 网络协议: TCP连接状态(已连接、等待数据、已关闭)。
为啥用Generator?
你可能会问,实现状态机的方法多了去了,为啥非得用Generator呢? 理由如下:
- 代码更清晰,更易读: Generator可以将每个状态的逻辑独立出来,代码结构更清晰,更容易理解和维护。
- 控制流程更灵活: 通过
yield
关键字,可以精确控制状态的转换,想什么时候切换就什么时候切换。 - 状态切换更自然: Generator的暂停和恢复执行的特性,使得状态切换更加自然,避免了回调地狱。
Generator的基本概念
在开始之前,咱们先复习一下Generator的基本概念。 如果你已经很熟悉了,可以直接跳过。
- Generator函数: 用
function*
声明的函数,与普通函数不同的是,它可以暂停执行,并在稍后恢复执行。 yield
关键字: 用于在Generator函数中暂停执行,并将一个值返回给调用者。next()
方法: 用于恢复Generator函数的执行,并传递一个值给Generator函数。
function* myGenerator() {
console.log("开始执行");
yield 1;
console.log("执行到yield 1");
yield 2;
console.log("执行到yield 2");
return 3;
}
const gen = myGenerator();
console.log(gen.next()); // 输出: 开始执行 { value: 1, done: false }
console.log(gen.next()); // 输出: 执行到yield 1 { value: 2, done: false }
console.log(gen.next()); // 输出: 执行到yield 2 { value: 3, done: true }
console.log(gen.next()); // 输出: { value: undefined, done: true }
状态机实战:简单的交通灯
咱们先来一个最简单的例子:用Generator实现一个交通灯状态机。
function* trafficLight() {
while (true) {
console.log("红灯");
yield "red";
console.log("绿灯");
yield "green";
console.log("黄灯");
yield "yellow";
}
}
const light = trafficLight();
console.log(light.next()); // 输出: 红灯 { value: 'red', done: false }
console.log(light.next()); // 输出: 绿灯 { value: 'green', done: false }
console.log(light.next()); // 输出: 黄灯 { value: 'yellow', done: false }
console.log(light.next()); // 输出: 红灯 { value: 'red', done: false }
// ... 循环往复
这个例子很简单,但它已经展示了Generator如何控制状态的转换。 每次调用light.next()
,交通灯就会切换到下一个状态。
状态机进阶:带参数的状态转换
上面的例子只能按照固定的顺序切换状态,不够灵活。 咱们来一个更高级的例子:带参数的状态转换。
假设我们要实现一个简单的电梯状态机。 电梯有以下状态:
- idle: 待机状态
- moving: 移动状态
- stopped: 停止状态
电梯的状态转换规则如下:
- 从idle状态可以移动到moving状态,需要指定目标楼层。
- 从moving状态可以移动到stopped状态,到达目标楼层后自动停止。
- 从stopped状态可以移动到idle状态,或者移动到moving状态。
function* elevator() {
let currentFloor = 1; // 初始楼层
let targetFloor = null; // 目标楼层
let state = 'idle'; // 初始状态
while (true) {
switch (state) {
case 'idle':
console.log(`电梯处于待机状态,当前楼层:${currentFloor}`);
targetFloor = yield { state: 'idle', floor: currentFloor }; // 等待目标楼层
if (targetFloor !== null && targetFloor !== undefined && targetFloor !== currentFloor) {
state = 'moving'; // 切换到移动状态
}
break;
case 'moving':
console.log(`电梯正在移动,当前楼层:${currentFloor},目标楼层:${targetFloor}`);
// 模拟电梯移动
while (currentFloor !== targetFloor) {
currentFloor += (targetFloor > currentFloor) ? 1 : -1;
console.log(`电梯到达 ${currentFloor} 层`);
yield { state: 'moving', floor: currentFloor, target: targetFloor }; // 每次移动都 yield
}
state = 'stopped'; // 切换到停止状态
break;
case 'stopped':
console.log(`电梯已停止,当前楼层:${currentFloor}`);
yield { state: 'stopped', floor: currentFloor }; // 停止状态
state = 'idle'; // 切换到待机状态
break;
default:
console.log("未知状态");
return;
}
}
}
const myElevator = elevator();
// 启动电梯
console.log(myElevator.next()); // 输出: 电梯处于待机状态,当前楼层:1 { value: { state: 'idle', floor: 1 }, done: false }
// 去5楼
console.log(myElevator.next(5)); // 输出: 电梯正在移动,当前楼层:1,目标楼层:5 { value: { state: 'moving', floor: 2, target: 5 }, done: false }
console.log(myElevator.next()); // 输出: 电梯到达 2 层 { value: { state: 'moving', floor: 3, target: 5 }, done: false }
console.log(myElevator.next()); // 输出: 电梯到达 3 层 { value: { state: 'moving', floor: 4, target: 5 }, done: false }
console.log(myElevator.next()); // 输出: 电梯到达 4 层 { value: { state: 'moving', floor: 5, target: 5 }, done: false }
console.log(myElevator.next()); // 输出: 电梯已停止,当前楼层:5 { value: { state: 'stopped', floor: 5 }, done: false }
console.log(myElevator.next()); // 输出: 电梯处于待机状态,当前楼层:5 { value: { state: 'idle', floor: 5 }, done: false }
// 再次去1楼
console.log(myElevator.next(1));
这个例子稍微复杂一些,但它展示了如何使用next()
方法传递参数给Generator函数,从而控制状态的转换。
状态机设计原则
在设计状态机时,需要遵循一些基本原则,才能保证状态机的正确性和可维护性。
- 明确状态: 首先要明确系统有哪些状态。
- 定义转换规则: 明确状态之间如何转换,以及转换的条件。
- 避免循环依赖: 状态之间的转换关系应该是有向的,避免出现循环依赖。
- 处理异常情况: 考虑各种异常情况,并定义相应的处理机制。
状态机模式:状态模式
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。 状态模式的核心思想是将状态封装成独立的类,并将状态的转换委托给Context对象。
// 状态接口
class State {
constructor(name) {
this.name = name;
}
enter() {
console.log(`进入 ${this.name} 状态`);
}
execute() {
console.log(`执行 ${this.name} 状态`);
}
exit() {
console.log(`退出 ${this.name} 状态`);
}
}
// 具体状态类
class IdleState extends State {
constructor() {
super("待机");
}
execute(context) {
console.log("待机状态,等待指令");
return "moving"; // 返回下一个状态
}
}
class MovingState extends State {
constructor() {
super("移动");
}
execute(context) {
console.log("移动状态,前往目标楼层");
return "stopped"; // 返回下一个状态
}
}
class StoppedState extends State {
constructor() {
super("停止");
}
execute(context) {
console.log("停止状态,电梯已到达");
return "idle"; // 返回下一个状态
}
}
// 环境类
class Context {
constructor() {
this.states = {
idle: new IdleState(),
moving: new MovingState(),
stopped: new StoppedState(),
};
this.currentState = this.states.idle; // 初始状态
}
transition(stateName) {
if (this.states[stateName]) {
this.currentState.exit(); // 退出当前状态
this.currentState = this.states[stateName]; // 切换到新状态
this.currentState.enter(); // 进入新状态
} else {
console.log("无效状态");
}
}
run() {
let nextState = this.currentState.execute(this); // 执行当前状态
if (nextState) {
this.transition(nextState); // 切换到下一个状态
}
}
}
// 使用
const context = new Context();
context.run(); // 待机状态,等待指令
context.run(); // 移动状态,前往目标楼层
context.run(); // 停止状态,电梯已到达
context.run(); // 待机状态,等待指令
用Generator实现状态模式
虽然状态模式通常使用类来实现,但我们也可以用Generator来实现类似的效果。
function* stateMachine() {
let state = 'idle';
while (true) {
switch (state) {
case 'idle':
console.log("待机状态,等待指令");
state = yield 'idle'; // 等待指令
break;
case 'moving':
console.log("移动状态,前往目标楼层");
state = yield 'moving'; // 执行移动
break;
case 'stopped':
console.log("停止状态,电梯已到达");
state = yield 'stopped'; // 停止
break;
default:
console.log("无效状态");
return;
}
}
}
const sm = stateMachine();
sm.next(); // 启动状态机,进入idle状态
sm.next('moving'); // 切换到moving状态
sm.next('stopped'); // 切换到stopped状态
sm.next('idle'); // 切换到idle状态
这个例子虽然简单,但它展示了如何用Generator实现状态模式的核心思想:将状态的逻辑独立出来,并通过yield
关键字控制状态的转换。
状态机库
如果你不想自己手写状态机,也可以使用现成的状态机库。 比较流行的JS状态机库有:
库名称 | 描述 |
---|---|
XState | 功能强大的状态机库,支持分层状态、并行状态、历史状态等高级特性。 |
JavaScript State Machine | 简单易用的状态机库,API简洁明了。 |
machina.js | 轻量级的状态机库,专注于核心功能。 |
这些库都提供了丰富的功能和API,可以帮助你更方便地构建复杂的状态机。
总结
今天咱们一起学习了如何使用JS的Generator实现状态机。 相信你已经掌握了以下知识点:
- 状态机的基本概念和应用场景。
- Generator的基本概念和用法。
- 如何使用Generator实现简单的状态机。
- 如何使用Generator实现带参数的状态转换。
- 状态机的设计原则。
- 状态模式的核心思想。
- 如何用Generator实现状态模式。
- 常用的JS状态机库。
希望今天的分享对你有所帮助。 记住,理论是基础,实践才是王道。 多写代码,多练习,才能真正掌握状态机的精髓。
下次有机会,咱们再聊聊更高级的状态机技巧! 拜拜!