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