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

 更新时间:2022年09月07日 17:20:07   作者:blazer  
这篇文章主要为大家介绍了JS前端实现fsm有限状态机实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

我们平时开发时本质上就时对应用程序的各种状态进行切换并作出相应处理,最直接的方法就是添加标志位然后考虑所有可能出现的边界问题,通过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",
  },
});
  • 首先初始时door的状态显示为红灯即RED
  • 当我们进行yello操作的时候,状态变成黄灯,即状态改变为YELLO
  • 当我们进行green操作的时候,状态变成绿灯,即状态改变为GREEN
  • 当我们连着进行red操作、yello操作的时候,最终状态变成黄灯,即状态改变为YELLO ...

从零开始

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

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

获取状态

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

  • getMachineState:获取当前的状态
  • getMachineEvents:获取当前状态上保存了哪些方法

这两个函数通过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会作为结果进行返回),这样就可以

  • 使用.yello().red().green()的方法,改变状态
  • 使用.getMachineState().getMachineEvents()查看当前状态和查看当前状态对应的方法
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",
  },
});

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

  • door.getMachineState() --> RED
  • door.yello().getMachineState() --> YELLO
  • door.green().getMachineState() --> GREEN
  • door.red().yello().getMachineState() --> YELLO

实现钩子函数

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

对此,可以对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有限状态机的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Anyscript开发指南绕过typescript类型检查

    详解Anyscript开发指南绕过typescript类型检查

    这篇文章主要为大家介绍了详解Anyscript开发指南绕过typescript类型检查,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • JavaScript 反射学习技巧

    JavaScript 反射学习技巧

    这篇文章主要给大家分享的是JavaScript 的反射学习技巧,主要是区别在于所有的函数对象属性过于复杂,而且额外增加可能会导致程序行为不合理,所以扩展 Reflect 函数来专门对函数对象处理调用方法,构造对象,获取或者设置属性等相关操作。下面一起进入文章内容吧
    2021-10-10
  • 微信小程序 sha1 实现密码加密实例详解

    微信小程序 sha1 实现密码加密实例详解

    这篇文章主要介绍了微信小程序 sha1 实现密码加密实例详解的相关资料,需要的朋友可以参考下
    2017-07-07
  • 移动开发之自适应手机屏幕宽度

    移动开发之自适应手机屏幕宽度

    这篇文章主要介绍了移动开发之自适应手机屏幕宽度的相关资料,网上关于这方面的文章有很多,重复的东西本文不再赘述,仅提供思路,并解释一些其他文章讲述模糊的地方,需要的朋友可以参考下
    2016-11-11
  • 详解微信小程序 wx.uploadFile 的编码坑

    详解微信小程序 wx.uploadFile 的编码坑

    编写微信小程序时,用到 wx.uploadFile,用来上传图片+文本信息,本篇文章主要介绍了微信小程序 wx.uploadFile 的编码坑,有兴趣的可以了解一下。
    2017-01-01
  • 微信小程序 input表单与redio及下拉列表的使用实例

    微信小程序 input表单与redio及下拉列表的使用实例

    这篇文章主要介绍了微信小程序 input表单与redio及下拉列表的使用实例的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-09-09
  • JS实现简单的操作杆旋转示例详解

    JS实现简单的操作杆旋转示例详解

    这篇文章主要为大家介绍了JS实现简单的操作杆旋转示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 微信小程序之获取当前位置经纬度以及地图显示详解

    微信小程序之获取当前位置经纬度以及地图显示详解

    最近刚开始接触微信小程序,在弄懂其结构以及相关接口之后,准备着手实现一个小程序,功能包括--获取用户当前位置的经纬度,在地图上查看位置,通过地图获取不同位置的经纬度。
    2017-05-05
  • 一文详解webpack中loader与plugin的区别

    一文详解webpack中loader与plugin的区别

    这篇文章主要为大家介绍了一文详解webpack中loader与plugin的区别详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Parcel配置public静态文件目录过程解析

    Parcel配置public静态文件目录过程解析

    这篇文章主要为大家介绍了Parcel配置public静态文件目录实现过程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06

最新评论