JS前端實現(xiàn)fsm有限狀態(tài)機(jī)實例詳解
引言
我們平時開發(fā)時本質(zhì)上就時對應(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)的管理也會很容易,也是更好的實踐了UI=fn(state)思想。
舉個栗子??
我們這里用一個簡易的紅綠燈案例,實現(xiàn)一個簡易的有限狀態(tài)機(jī),并且可以通過每一個狀態(tài)暴露出來的方法,改變當(dāng)前的狀態(tài)
const door = machine({
RED: {
yello: "YELLO",
},
GREEN: {
red: "RED",
},
YELLO: {
green: "GREEN",
},
});
- 首先初始時
door的狀態(tài)顯示為紅燈即RED - 當(dāng)我們進(jìn)行
yello操作的時候,狀態(tài)變成黃燈,即狀態(tài)改變?yōu)?code>YELLO - 當(dāng)我們進(jìn)行
green操作的時候,狀態(tài)變成綠燈,即狀態(tài)改變?yōu)?code>GREEN - 當(dāng)我們連著進(jìn)行
red操作、yello操作的時候,最終狀態(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)
因為當(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)行改變的時候,調(diào)用的是一開始配置好的方法對狀態(tài)進(jìn)行更改,此時需要將每一個狀態(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,因為我們在getMachineState時,獲取到的是currentState.name,而不是currentState,所以此時在獲取狀態(tài)的時候需要用通過函數(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;
};
實現(xiàn)fsm狀態(tài)機(jī)
現(xiàn)在我們實現(xiàn)了一個完整的fsm,當(dāng)我們配置好狀態(tài)機(jī)時
const door = machine({
RED: {
yello: "YELLO",
},
GREEN: {
red: "RED",
},
YELLO: {
green: "GREEN",
},
});
執(zhí)行如下操作時,會打印我們想要的結(jié)果
door.getMachineState()--> REDdoor.yello().getMachineState()--> YELLOdoor.green().getMachineState()--> GREENdoor.red().yello().getMachineState()--> YELLO
實現(xiàn)鉤子函數(shù)
但是我們監(jiān)聽不到狀態(tài)機(jī)的改變,所以當(dāng)我們想監(jiān)聽狀態(tài)變換時,應(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)用時,狀態(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ù)
以上就是JS前端實現(xiàn)fsm有限狀態(tài)機(jī)實例詳解的詳細(xì)內(nèi)容,更多關(guān)于JS前端fsm有限狀態(tài)機(jī)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
http 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-03
競態(tài)條件Race condition及如何避免的三種方案詳解
這篇文章主要為大家介紹了競態(tài)條件Race condition及如何避免的三種方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

