狀態機詳解(未完待續)

什麼是狀態機(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()

此外,狀態機的概念還用於語言的詞法分析,語法分析,網絡協議,遊戲設計,客服機器人等各個方面,是咱們編程中一個很重要的概念

推薦工具:

參考連接:

相關文章
相關標籤/搜索