什麼是狀態機(FSM)?
狀態機(Finite State Machine)是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。javascript
字面上講它是用來管理狀態的,狀態數量是有限的,而且能夠自動執行,即給定一個當前狀態與輸入,能夠明確獲得輸出狀態,固然他不是一個機器,而是一個數學模型。html
它有四個概念:java
- State(狀態):一個狀態機至少要包含兩個狀態,而且狀態個數是有限的;
- Event(事件):執行某個操做的觸發條件或口令;
- Action(動做):事件發生之後要執行的動做,在咱們編程中通常就代指一個函數;
- Transition(變換):從一個狀態變到另外一個狀態。
其餘的理解: 狀態機可概括爲4個要素,即現態、條件、動做、次態。「現態」和「條件」是因,「動做」和「次態」是果。詳解以下:git
- 現態:是指當前所處的狀態。
- 條件:又稱爲「事件」。當一個條件被知足,將會觸發一個動做,或者執行一次狀態的遷移。
- 動做:條件知足後執行的動做。動做執行完畢後,能夠遷移到新的狀態,也能夠仍舊保持原狀態。動做不是必需的,當條件知足後,也能夠不執行任何動做,直接遷移到新狀態。
- 次態:條件知足後要遷往的新狀態。「次態」是相對於「現態」而言的,「次態」一旦被激活,就轉變成新的「現態」了。
狀態機能用來作什麼
正則表達式引擎
正則表達是引擎是一個典型的狀態機的栗子,它分爲非肯定有限狀態自動機NFA(Nondeterministic Finite Automaton)
與肯定有限狀態自動機DFA(Deterministic Finite Automaton)
兩種,都統統過狀態機的概念來實現的。github
簡單實現一個關於ab|bc
的正則匹配:正則表達式
function match(string) { let state = start; for (let s of string) { console.log('s==>',s); state = state(s); } return state === end; } function start(s) { if (s === 'a') { return foundB1; } if (s === 'b') { return foundB2; } } function end(s) { return end; } function foundB1(s) { if (s === 'b') { return end; } return start(s); } function foundB2(s) { if (s === 'c') { return end; } return start(s); } const string = 'ab'; console.log(match(string));
KMP算法(Knuth–Morris–Pratt algorithm)
以字符串"BBC ABCDAB ABCDABCDABDE"與搜索詞"ABCDABD"進行舉例算法
移動位數 = 已匹配的字符數 - 對應的部分匹配值編程
Promise
手擼一個Promise的實現:json
function Promise(executor) { var _this = this this.state = 'PENDING'; //狀態 this.value = undefined; //成功結果 this.reason = undefined; //失敗緣由 this.onFulfilled = [];//成功的回調 this.onRejected = []; //失敗的回調 function resolve(value) { if(_this.state === 'PENDING'){ _this.state = 'FULFILLED' _this.value = value _this.onFulfilled.forEach(fn => fn(value)) } } function reject(reason) { if(_this.state === 'PENDING'){ _this.state = 'REJECTED' _this.reason = reason _this.onRejected.forEach(fn => fn(reason)) } } try { executor(resolve, reject); } catch (e) { reject(e); } } Promise.prototype.then = function (onFulfilled, onRejected) { if(this.state === 'FULFILLED'){ typeof onFulfilled === 'function' && onFulfilled(this.value) } if(this.state === 'REJECTED'){ typeof onRejected === 'function' && onRejected(this.reason) } if(this.state === 'PENDING'){ typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled) typeof onRejected === 'function' && this.onRejected.push(onRejected) } };
HTTP協議
簡單封裝一個http請求的響應解析器:設計模式
const net = require('net'); const images = require('images'); const parser = require('./parser'); const render = require('./render'); class Request { constructor(options) { this.method = options.method || 'GET'; this.host = options.host; this.port = options.port || 80; this.path = options.path || '/'; this.body = options.body || {}; this.headers = options.headers || {}; if (!this.headers['Content-Type']) { this.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } if (this.headers['Content-Type'] == 'application/json') { this.bodyText = JSON.stringify(this.body); } else if (this.headers['Content-Type'] === 'application/x-www-form-urlencoded') { this.bodyText = Object.keys(this.body).map(key => `${key}=${encodeURIComponent(this.body[key])}`).join('&'); } this.headers['Content-Length'] = this.bodyText.length; } send(connection) { return new Promise((resolve, reject) => { // do something let parser = new ResponseParser(); if (connection) { connection.write(this.toString()); } else { connection = net.createConnection({ host: this.host, port: this.port }, () => { connection.write(this.toString()); }); } connection.on('data', data => { parser.receive(data.toString()); console.log(parser.isFinished,'isFinished'); if (parser.isFinished) { resolve(parser.response); connection.end(); } }); connection.on('error', err => { reject(err); connection.end(); }); }); } toString() { return `${this.method} ${this.path} HTTP/1.1\r ${Object.keys(this.headers).map(key => `${key}: ${this.headers[key]}`).join('\r\n')}\r \r ${this.bodyText}`; } }; class ResponseParser { constructor() { this.WAITING_STATUS_LINE = 0; this.WAITING_STATUS_LINE_END = 1; this.WAITING_HEADER_NAME = 2; this.WAITING_HEADER_SPACE = 3; this.WAITING_HEADER_VALUE = 4; this.WAITING_HEADER_LINE_END = 5; this.WAITING_HEADER_BLOCK_END = 6; this.WAITING_BODY = 7; this.current = this.WAITING_STATUS_LINE; this.statusLine = ''; this.headers = {}; this.headerName = ''; this.headerValue = ''; this.bodyParser = null; } get isFinished() { return this.bodyParser && this.bodyParser.isFinished; } get response() { this.statusLine.match(/HTTP\/1.1 ([0-9]+) ([\s\S]+)/); return { statusCode: RegExp.$1, statusText: RegExp.$2, headers: this.headers, body: this.bodyParser.content.join('') } } receive(string) { for (let i = 0; i < string.length; i++) { this.receiveChar(string.charAt(i)); } } receiveChar(char) { if (this.current === this.WAITING_STATUS_LINE) { if (char === '\r') { this.current = this.WAITING_STATUS_LINE_END; } else { this.statusLine += char; } } else if (this.current === this.WAITING_STATUS_LINE_END) { if (char === '\n') { this.current = this.WAITING_HEADER_NAME; } } else if (this.current === this.WAITING_HEADER_NAME) { if (char === ':') { this.current = this.WAITING_HEADER_SPACE; } else if (char === '\r') { this.current = this.WAITING_HEADER_BLOCK_END; if (this.headers['Transfer-Encoding'] === 'chunked') { this.bodyParser = new TrunkedBodyParser(); } } else { this.headerName += char; } } else if (this.current === this.WAITING_HEADER_SPACE) { if (char === ' ') { this.current = this.WAITING_HEADER_VALUE; } } else if (this.current === this.WAITING_HEADER_VALUE) { if (char === '\r') { this.current = this.WAITING_HEADER_LINE_END; this.headers[this.headerName] = this.headerValue; this.headerName = ''; this.headerValue = ''; } else { this.headerValue += char; } } else if (this.current === this.WAITING_HEADER_LINE_END) { if (char === '\n') { this.current = this.WAITING_HEADER_NAME; } } else if (this.current === this.WAITING_HEADER_BLOCK_END) { if (char === '\n') { this.current = this.WAITING_BODY; } } else if (this.current === this.WAITING_BODY) { this.bodyParser.receiveChar(char); } } } class TrunkedBodyParser { constructor() { this.WAITING_LENGTH = 0; this.WAITING_LENGTH_LINE_END = 1; this.READING_TRUNK = 2; this.WAITING_NEW_LINE = 3; this.WAITING_NEW_LINE_END = 4; this.length = 0; this.content = []; this.isFinished = false; this.current = this.WAITING_LENGTH; } receiveChar(char) { if (this.current === this.WAITING_LENGTH) { if (char === '\r') { if (this.length === 0) { this.isFinished = true; } this.current = this.WAITING_LENGTH_LINE_END; } else { this.length *= 16; this.length += parseInt(char, 16); } } else if (this.current === this.WAITING_LENGTH_LINE_END) { if (char === '\n') { this.current = this.READING_TRUNK; } } else if (this.current === this.READING_TRUNK) { this.content.push(char); this.length--; if (this.length === 0) { this.current = this.WAITING_NEW_LINE; } } else if (this.current === this.WAITING_NEW_LINE) { if (char === '\r') { this.current = this.WAITING_NEW_LINE_END; } } else if (this.current === this.WAITING_NEW_LINE_END) { if (char === '\n') { this.current = this.WAITING_LENGTH; } } } } void async function() { let request = new Request({ method: 'POST', host: '127.0.0.1', port: 8088, path: '/', headers: { ['X-Foo2']: 'customed' }, body: { name: 'test' } }); let response = await request.send(); let dom = parser.parseHTML(response.body); let viewport = images(800, 600); render(viewport, dom.children[0].children[3].children[1].children[3]); viewport.save("viewport.jpg"); console.log(JSON.stringify(dom, null, ' '), 'dom'); }();
HTML解析
在html標準裏邊,html的詞法描述就是按照狀態機的概念來進行描述的,詳細;
設計模式--狀態模式
容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。其別名爲狀態對象(Objects for States),狀態模式是一種對象行爲型模式。
設計模式中有個狀態模式,還有一個策略模式,二者有點類似,又有些不一樣,它們的相同點是,都有一個上下文、一些策略類或者狀態類,上下文把請求委託給這些類來執行。它們之間的區別是策略模式中的各個策略類之間是平等又平行的,它們之間沒有任何關係,因此客戶必須熟知這些策略類的做用,以便客戶本身能夠隨時主動切換算法。可是在狀態模式中,狀態和狀態對應的行爲早已被封裝好,狀態之間的切換也早就被規定,「改變行爲」這件事發生在狀態模式的內部,對於客戶來講,不須要了解這些細節。
講的多都不如直接看代碼,那麼咱們經過栗子來理解一下狀態模式的應用:
一個比較常見的場景栗子電燈只有一個開關,可是它的表現是:第一次按下打開弱光,第二次按下打開強光,第三次纔是關閉電燈。
未使用狀態模式:
class Light { construct () { this.state = 'off' this.button = null } // 建立一個button負責控制電燈的開關 init () { const button = document.createElement('button') this.button = document.body.appendChild(button) this.button.innerHTML = '開關' this.button.onclick = () => { this.buttonWasPressed() } } buttonWasPressed () { if (this.state === 'off') { console.log('弱光') this.state = 'weak' } else if (this.state === 'weak') { console.log('強光') this.state = 'strong' } else if (this.state === 'strong') { console.log('關燈') this.state = 'off' } } } const light = new Light() light.init()
使用狀態模式的代碼:
class OffLightState { construct (light) { this.light = light } buttonWasPressed () { console.log('弱光') this.light.setState(this.light.weakLightState) } } class WeakLightState { construct (light) { this.light = light } buttonWasPressed () { console.log('強光') this.light.setState(this.light.strongLightState) } } class StrongLightState { construct (light) { this.light = light } buttonWasPressed () { console.log('關燈') this.light.setState(this.light.offLightState) } } // light類 class Light { construct () { this.offLightState = new OffLightState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.currentState = this.offLightState // 初始化電燈狀態 this.button = null } init () { const button = document.createElement('button') this.button = document.body.appendChild(button) this.button.innerHTML = '開關' this.button.onclick = () => { this.currentState.buttonWasPressed() } } setState (newState) { this.currentState = newState } } const light = new Light() light.init()
此外,狀態機的概念還用於語言的詞法分析,語法分析,網絡協議,遊戲設計,客服機器人等各個方面,是咱們編程中一個很重要的概念
推薦工具:
- Javascript Finite State Machine--一個功能強大的狀態機函數庫
參考連接: