JS前端實(shí)現(xiàn)fsm有限狀態(tài)機(jī)實(shí)例詳解
引言
我們平時(shí)開發(fā)時(shí)本質(zhì)上就時(shí)對應(yīng)用程序的各種狀態(tài)進(jìn)行切換并作出相應(yīng)處理,最直接的方法就是添加標(biāo)志位然后考慮所有可能出現(xiàn)的邊界問題,通過if...else if...else 來對當(dāng)前狀態(tài)進(jìn)行判斷從而達(dá)成頁面的交互效果, 但隨著業(yè)務(wù)需求的增加各種狀態(tài)也會隨之增多,我們就不得不再次修改if...else代碼或者增加對應(yīng)的判斷,最終使得程序的可讀性、擴(kuò)展性、維護(hù)性變得很麻煩
有限狀態(tài)機(jī),(英語:Finite-state machine, FSM),又稱有限狀態(tài)自動機(jī),簡稱狀態(tài)機(jī),是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。
利用有限狀態(tài)機(jī)
我們可以將條件判斷的結(jié)果轉(zhuǎn)化為狀態(tài)對象內(nèi)部的狀態(tài),并且能夠使用對應(yīng)的方法,進(jìn)行對應(yīng)的改變。這樣方便了對狀態(tài)的管理也會很容易,也是更好的實(shí)踐了UI=fn(state)
思想。
舉個栗子??
我們這里用一個簡易的紅綠燈
案例,實(shí)現(xiàn)一個簡易的有限狀態(tài)機(jī)
,并且可以通過每一個狀態(tài)暴露出來的方法,改變當(dāng)前的狀態(tài)
const door = machine({ RED: { yello: "YELLO", }, GREEN: { red: "RED", }, YELLO: { green: "GREEN", }, });
- 首先初始時(shí)
door
的狀態(tài)顯示為紅燈即RED
- 當(dāng)我們進(jìn)行
yello
操作的時(shí)候,狀態(tài)變成黃燈,即狀態(tài)改變?yōu)?code>YELLO - 當(dāng)我們進(jìn)行
green
操作的時(shí)候,狀態(tài)變成綠燈,即狀態(tài)改變?yōu)?code>GREEN - 當(dāng)我們連著進(jìn)行
red
操作、yello
操作的時(shí)候,最終狀態(tài)變成黃燈,即狀態(tài)改變?yōu)?code>YELLO ...
從零開始
通過接受一個對象(如果是函數(shù)就執(zhí)行),拿到初始值,并且在函數(shù)內(nèi)部維護(hù)一個變量記錄當(dāng)前的狀態(tài),并且記錄第一個狀態(tài)為初始狀態(tài)
const machine = statesObject => { if (typeof statesObject == "function") statesObject = statesObject(); let currentState; for (const stateName in statesObject) { currentState = currentState || statesObject[stateName]; } };
獲取狀態(tài)
因?yàn)楫?dāng)前狀態(tài)是通過函數(shù)局部變量currentState
進(jìn)行保存,我們需要一些方法
getMachineState
:獲取當(dāng)前的狀態(tài)getMachineEvents
:獲取當(dāng)前狀態(tài)上保存了哪些方法
這兩個函數(shù)通過stateMachine
進(jìn)行保存并作為函數(shù)結(jié)果進(jìn)行返回
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 };
狀態(tài)改變
我們進(jìn)行改變的時(shí)候,調(diào)用的是一開始配置好的方法對狀態(tài)進(jìn)行更改,此時(shí)需要將每一個狀態(tài)合并到stateStore
中進(jìn)行保存
再將對應(yīng)的方法作為偏函數(shù)(函數(shù)預(yù)先將轉(zhuǎn)換的狀態(tài)和方法進(jìn)行傳遞),保存在stateMachine
(stateMachine
會作為結(jié)果進(jìn)行返回),這樣就可以
- 使用
.yello()
、.red()
、.green()
的方法,改變狀態(tài) - 使用
.getMachineState()
、.getMachineEvents()
查看當(dāng)前狀態(tài)和查看當(dāng)前狀態(tài)對應(yīng)的方法
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
函數(shù),即改變當(dāng)前狀態(tài),在stateStore
中獲取當(dāng)前的要更改的狀態(tài)名,重新給currentState
賦值,并返回stateMachine
供函數(shù)繼續(xù)鏈?zhǔn)秸{(diào)用
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; };
看似沒有問題,但是如果我們按照上面的代碼執(zhí)行后,獲得的狀態(tài)值為undefined
,因?yàn)槲覀冊?code>getMachineState時(shí),獲取到的是currentState.name
,而不是currentState
,所以此時(shí)在獲取狀態(tài)的時(shí)候需要用通過函數(shù)進(jìn)行獲取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; };
實(shí)現(xiàn)fsm
狀態(tài)機(jī)
現(xiàn)在我們實(shí)現(xiàn)了一個完整的fsm
,當(dāng)我們配置好狀態(tài)機(jī)時(shí)
const door = machine({ RED: { yello: "YELLO", }, GREEN: { red: "RED", }, YELLO: { green: "GREEN", }, });
執(zhí)行如下操作時(shí),會打印我們想要的結(jié)果
door.getMachineState()
--> REDdoor.yello().getMachineState()
--> YELLOdoor.green().getMachineState()
--> GREENdoor.red().yello().getMachineState()
--> YELLO
實(shí)現(xiàn)鉤子函數(shù)
但是我們監(jiān)聽不到狀態(tài)機(jī)的改變,所以當(dāng)我們想監(jiān)聽狀態(tài)變換時(shí),應(yīng)該從內(nèi)部暴露出鉤子函數(shù),這樣可以監(jiān)聽到狀態(tài)機(jī)內(nèi)部的變化,又能進(jìn)行一些副作用操作
對此,可以對transition
進(jìn)行一些改造,將對于currentState
狀態(tài)的改變用方法setMachineState
去處理
setMachineState
函數(shù)會攔截當(dāng)前狀態(tài)上綁定onChange
方法進(jìn)行觸發(fā),并將改變狀態(tài)的函數(shù)
、改變前的狀態(tài)
、改變后的狀態(tài)
傳遞出去
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; };
這樣我們在調(diào)用時(shí),狀態(tài)的每一次改變都可以監(jiān)聽到,并且可以執(zhí)行對應(yīng)的副作用函數(shù)
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; };
總結(jié)
這個 fsm有限狀態(tài)機(jī)
主要完成了:
- 狀態(tài)的可觀測
- 狀態(tài)的鏈?zhǔn)秸{(diào)用
- 狀態(tài)變化的鉤子函數(shù)
項(xiàng)目代碼:github.com/blazer233/a…
以上就是JS前端實(shí)現(xiàn)fsm有限狀態(tài)機(jī)實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于JS前端fsm有限狀態(tài)機(jī)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序手勢操作之單觸摸點(diǎn)與多觸摸點(diǎn)
這篇文章主要介紹了微信小程序手勢操作之單觸摸點(diǎn)與多觸摸點(diǎn)的相關(guān)資料,需要的朋友可以參考下2017-03-03http proxy 對網(wǎng)絡(luò)請求進(jìn)行代理使用詳解
這篇文章主要為大家介紹了http proxy 對網(wǎng)絡(luò)請求進(jìn)行代理使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09基于遷移學(xué)習(xí)的JS目標(biāo)檢測器構(gòu)建過程詳解
這篇文章主要為大家介紹了基于遷移學(xué)習(xí)的JS目標(biāo)檢測器構(gòu)建過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03C#微信小程序服務(wù)端獲取用戶解密信息實(shí)例代碼
這篇文章主要介紹了 C#微信小程序服務(wù)端獲取用戶解密信息實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03競態(tài)條件Race condition及如何避免的三種方案詳解
這篇文章主要為大家介紹了競態(tài)條件Race condition及如何避免的三種方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例
這篇文章主要介紹了微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05