文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

JS前端实现fsm有限状态机实例详解

2024-04-02 19:55

关注

引言

我们平时开发时本质上就时对应用程序的各种状态进行切换并作出相应处理,最直接的方法就是添加标志位然后考虑所有可能出现的边界问题,通过if...else if...else 来对当前状态进行判断从而达成页面的交互效果, 但随着业务需求的增加各种状态也会随之增多,我们就不得不再次修改if...else代码或者增加对应的判断,最终使得程序的可读性、扩展性、维护性变得很麻烦

有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

利用有限状态机我们可以将条件判断的结果转化为状态对象内部的状态,并且能够使用对应的方法,进行对应的改变。这样方便了对状态的管理也会很容易,也是更好的实践了UI=fn(state)思想。

举个栗子?

我们这里用一个简易的红绿灯案例,实现一个简易的有限状态机,并且可以通过每一个状态暴露出来的方法,改变当前的状态

const door = machine({
  RED: {
    yello: "YELLO",
  },
  GREEN: {
    red: "RED",
  },
  YELLO: {
    green: "GREEN",
  },
});

从零开始

通过接受一个对象(如果是函数就执行),拿到初始值,并且在函数内部维护一个变量记录当前的状态,并且记录第一个状态为初始状态

const machine = statesObject => {
  if (typeof statesObject == "function") statesObject = statesObject();
  let currentState;
  for (const stateName in statesObject) {
    currentState = currentState || statesObject[stateName];
  }
};

获取状态

因为当前状态是通过函数局部变量currentState进行保存,我们需要一些方法

这两个函数通过stateMachine进行保存并作为函数结果进行返回

const machine = statesObject => {
  let currentState
  ...
  const getMachineState = () => currentState.name;
  const getMachineEvents = () => {
    const events = [];
    for (const property in currentState) {
      if (typeof currentState[property] == "function") events.push(property);
    }
    return events;
  };
  const stateMachine = { getMachineState, getMachineEvents };
  ...
  return stateMachine
};

状态改变

我们进行改变的时候,调用的是一开始配置好的方法对状态进行更改,此时需要将每一个状态合并到stateStore中进行保存

再将对应的方法作为偏函数(函数预先将转换的状态和方法进行传递),保存在stateMachinestateMachine会作为结果进行返回),这样就可以

const machine = statesObject => {
  if (typeof statesObject == "function") statesObject = statesObject();
  let currentState;
  const stateStore = {};
  const getMachineState = () => currentState.name;
  const getMachineEvents = () => {
    const events = [];
    for (const property in currentState) {
      if (typeof currentState[property] == "function") events.push(property);
    }
    return events;
  };
  const stateMachine = { getMachineState, getMachineEvents };
  for (const stateName in statesObject) {
    stateStore[stateName] = statesObject[stateName];
    for (const event in stateStore[stateName]) {
      stateMachine[event] = transition.bind(null, stateName, event);
    }
    stateStore[stateName].name = stateName;
    currentState = currentState || stateStore[stateName];
  }
  return stateMachine;
};

transition

上面代码中最重要的莫过于transition函数,即改变当前状态,在stateStore中获取当前的要更改的状态名,重新给currentState赋值,并返回stateMachine供函数继续链式调用

const machine = statesObject => {
  ...
  const transition = (stateName, eventName) => {
    currentState = stateStore[stateName][eventName];
    return stateMachine;
  };
  for (const stateName in statesObject) {
    stateStore[stateName] = statesObject[stateName];
    for (const event in stateStore[stateName]) {
      stateMachine[event] = transition.bind(null, stateName, event);
    }
    stateStore[stateName].name = stateName;
    currentState = currentState || stateStore[stateName];
  }
  return stateMachine;
};

看似没有问题,但是如果我们按照上面的代码执行后,获得的状态值为undefined,因为我们在getMachineState时,获取到的是currentState.name,而不是currentState,所以此时在获取状态的时候需要用通过函数进行获取obj => obj[xxx]

const machine = statesObject => {
  ...
  const transition = (stateName, eventName) => {
    currentState = stateStore[stateName][eventName](stateStore);
    return stateMachine;
  };
  for (const stateName in statesObject) {
    stateStore[stateName] = statesObject[stateName];
    for (const event in stateStore[stateName]) {
      const item = stateStore[stateName][event];
      if (typeof item == "string") {
        stateStore[stateName][event] = obj => obj[item];
        stateMachine[event] = transition.bind(null, stateName, event);
      }
    }
    stateStore[stateName].name = stateName;
    currentState = currentState || stateStore[stateName];
  }
  return stateMachine;
};

实现fsm状态机

现在我们实现了一个完整的fsm,当我们配置好状态机时

const door = machine({
  RED: {
    yello: "YELLO",
  },
  GREEN: {
    red: "RED",
  },
  YELLO: {
    green: "GREEN",
  },
});

执行如下操作时,会打印我们想要的结果

实现钩子函数

但是我们监听不到状态机的改变,所以当我们想监听状态变换时,应该从内部暴露出钩子函数,这样可以监听到状态机内部的变化,又能进行一些副作用操作

对此,可以对transition进行一些改造,将对于currentState状态的改变用方法setMachineState去处理

setMachineState函数会拦截当前状态上绑定onChange方法进行触发,并将改变状态的函数改变前的状态改变后的状态传递出去

const machine = statesObject => {
  ...
  const setMachineState = (nextState, eventName) => {
    let onChangeState;
    let lastState = currentState;
    const resolveSpecialEventFn = (stateName, fnName) => {
      for (let property in stateStore[stateName]) {
        if (property.toLowerCase() === fnName.toLowerCase()) {
          return stateStore[stateName][property];
        }
      }
    };
    currentState = nextState;
    onChangeState = resolveSpecialEventFn(lastState.name, "onChange");
    if (
      onChangeState &&
      typeof onChangeState == "function" &&
      lastState.name != currentState.name
    ) {
      onChangeState.call(
        stateStore,
        eventName,
        lastState.name,
        currentState.name
      );
    }
  };
  const transition = (stateName, eventName) => {
    const curState = stateStore[stateName][eventName](stateStore);
    setMachineState(curState, eventName);
    return stateMachine;
  };
  ...
  return stateMachine;
};

这样我们在调用时,状态的每一次改变都可以监听到,并且可以执行对应的副作用函数

const door = machine({
  RED: {
    yello: "YELLO",
    onChange(fn, from, to) {
      console.log(fn, from, to, "onChange");
    },
  },
  GREEN: {
    red: "RED",
    onChange(fn, from, to) {
      console.log(fn, from, to, "onChange");
    },
  },
  YELLO: {
    green: "GREEN",
    onChange(fn, from, to) {
      console.log(fn, from, to, "onChange");
    },
  },
});

完整代码

export default statesObject => {
  if (typeof statesObject == "function") statesObject = statesObject();
  let currentState;
  const stateStore = {};
  const getMachineState = () => currentState.name;
  const getMachineEvents = () => {
    let events = [];
    for (const property in currentState) {
      if (typeof currentState[property] == "function") events.push(property);
    }
    return events;
  };
  const stateMachine = { getMachineState, getMachineEvents };
  const setMachineState = (nextState, eventName) => {
    let onChangeState;
    let lastState = currentState;
    const resolveSpecialEventFn = (stateName, fnName) => {
      for (let property in stateStore[stateName]) {
        if (property.toLowerCase() === fnName.toLowerCase()) {
          return stateStore[stateName][property];
        }
      }
    };
    currentState = nextState;
    onChangeState = resolveSpecialEventFn(lastState.name, "onChange");
    if (
      onChangeState &&
      typeof onChangeState == "function" &&
      lastState.name != currentState.name
    ) {
      onChangeState.call(
        stateStore,
        eventName,
        lastState.name,
        currentState.name
      );
    }
  };
  const transition = (stateName, eventName) => {
    const curState = stateStore[stateName][eventName](stateStore);
    setMachineState(curState, eventName);
    return stateMachine;
  };
  for (const stateName in statesObject) {
    stateStore[stateName] = statesObject[stateName];
    for (const event in stateStore[stateName]) {
      const item = stateStore[stateName][event];
      if (typeof item == "string") {
        stateStore[stateName][event] = obj => obj[item];
        stateMachine[event] = transition.bind(null, stateName, event);
      }
    }
    stateStore[stateName].name = stateName;
    currentState = currentState || stateStore[stateName];
  }
  return stateMachine;
};

总结

这个 fsm有限状态机 主要完成了:

项目代码:github.com/blazer233/a…

参考轮子:github.com/fschaefer/S…

以上就是JS前端实现fsm有限状态机实例详解的详细内容,更多关于JS前端fsm有限状态机的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯