web 开发中无处不在的状态机

学过编译原理的人都知道,词法分析和语法分析都用到了状态机,那是不是说状态机就和编译原理一样那么难学么?

答案是 no。状态机实际上在生活、网页、app、小程序中无处不在。

我这篇文章就以浅显易懂的方式讲解 web 开发中的状态机,并谈谈前端框架、spa。



状态机基本概念介绍

首先我们简单的介绍一下状态机的基本概念

wiki:有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

常见而简单的状态机如下:


图 from 阮一峰 JavaScript与有限状态机

我们用公式抽象一下:

末状态 = 初状态 + 触发条件

or

nextState = oldState + action

(是不是很像react,redux,对的其实react redux 就是管理状态机的一种方式,后面会介绍)



状态机的特征

我们观察一下上面的内容可以发现,状态机的三个特征

  • 状态总数是有限的。
  • 任一时刻,只处在一种状态之中。
  • 某种条件触发后,会从一种状态转变到另一种状态。

我们用 wiki 里的 展示了接受单词"nice"的有限状态自动机 例子来更具体了解一下状态机的实际运作过程(这是最简单的词法分析)。


图来自 wikizh.wikipedia.org/wiki/%




web 开发中的状态机

言归正传,介绍了一些基本的状态机知识后,我们来谈谈 web 开发中的状态机

据说一图胜千言,于是我画了一个很丑的图。。。

(点击就有放大高清的图片,我画了半个小时orz,你们不点我不服)


简单解释一下这个图

首先这个图里有三种级别的状态机:

  1. xx 系统
  2. xx 页面
  3. xx 组件

有三种 action:

  1. 点击链接:触发了系统的 state change
  2. 用户交互:触发了组件or页面的 state change
  3. 系统推送:(这个我没画到图里面),像 消息 这种 state 它在一个页面里自动的 change 就属于系统推送


前端开发与状态机

我们先从 js 实现一个最简单的状态机讲起。


js 实现一个最简单的状态机

这一个实现 from 阮一峰的文章 JavaScript与有限状态机

它对JavaScript的意义在于,很多对象可以写成有限状态机。

举例来说,网页上有一个菜单元素。鼠标悬停的时候,菜单显示;鼠标移开的时候,菜单隐藏。如果使用有限状态机描述,就是这个菜单只有两种状态(显示和隐藏),鼠标会引发状态转变。


 var menu = {
    // 当前状态
    currentState: 'hide',
  
    // 绑定事件
    initialize: function() {
      var self = this;
      self.on("hover", self.transition);
    },
  
    // 状态转换
    transition: function(event){
      switch(this.currentState) {
        case "hide":
          this.currentState = 'show';
          doSomething();
          break;
        case "show":
          this.currentState = 'hide';
          doSomething();
          break;
        default:
          console.log('Invalid State!');
          break;
      }
    }
  
  };


我们可以发现上面代码的的问题,比较低效和笨拙,把简单问题复杂化了。

所以,问题规模小的时候,if-else 就可以工作的很好了,但当问题规模一大,无论是大量的 if-else 还是问题 解决方案的本身 将会复杂庞大难以维护,这时候引入状态机的概念就非常行之有效了,并且当我们把状态机的实现交给 react redux 这种库上时,我们其实只用关心解决方案的本身,而不用自己去实现状态机。当我们只用关心解决方案本身的时候,解决问题的难度毫无疑问是变简单了的。



状态机和 前端框架

在 react、redux 不在的年代,其实我们使用 jquery 写 if-else 的时候,实际上就是在 自己造了一个 状态机并管理着,当代码量小的时候可能 jqurey if-else 比 react redux 这种写起来简单方便,但当状态机的规模大了的时候,同样也会发生上面所说的问题,相信用 jquery 写过单个页面 js 1000 行的人都深有感触,这时候使用 jqurey 写 if-else 的复杂度维护性都会达到一个可怕的难度上,当然这时候不少人会引入 MVC 类库 backbone 等,但 MVC 毕竟不是 reactive (响应式)思想的框架,从思想上看还是远不及 MVVM、react 这种响应式的编程方式简单和方便。

这也体现在前端框架之争中,jquery backbone 逐渐被 angular、vue、react 所代替,因为后者是更优的解决方案。

在 react 中,当问题的复杂性较小的时候,我们一般都不会去引入 redux ,因为此时 “Local state is fine”(redux 作者在 You Might Not Need Redux 的文章里这么说道 ),因为 redux 其实是一个复杂的 状态机的管理方案,它对于大规模的状态机管理是优于 react 的,但对于小规模的状态机,react 的 “Local state is fine”。

除了 状态机管理 的方面看 前端框架,“Local state is fine” 其实还有另一层含义。

local 是什么意思呢?

local 其实说的是 组件中的局部的意思,这里其实有着局部 vs 全局的一个大问题,在 angular、vue、react 中,组件的开发模式是一种更优的局部,或者说 jquery 其实是一种全局操作,虽然可以用各种方法来仿造去写局部的组件,但也是一种伪局部的组件,局部 vs 全局这里就不展开讲了,对于组件我们当然是希望它本身必须是 高内聚低耦合的,所以局部化是必须的。

所以我的观点是,新老前端框架最大的三个区别的点(以致于出现新老交替这种划时代技术浪潮):

  1. 状态机的更优管理方式
  2. 组件局部化的更优编写方式
  3. 响应式思想(实际上也就是 data driven view)


状态机、 spa、后端漫谈

当使用 spa 来开发系统级别的应用时,即便这个系统很小很小,可能就是一个简单的个人博客(比如我的博客 SimplyY 的博客:所有文章 ) ,都会引入巨大的复杂度,几乎必须要使用 redux 这种屠龙刀的技术,但对于 个人博客这种小系统,未免不会觉得有 高射炮打蚊子的感觉,还记得阿里 p9 交叉面试官对我的博客的技术选型提出的质疑,“你博客使用 react 、redux、spa 的技术选型是不是有问题,把简单问题复杂化了” 。这其实是一个非常好的问题,spa 其实把系统级别的 状态管理引入到了前端,我们还是看之前那张图




在一个系统里,页面和页面之间状态转换,是通过用户点击链接来的,这时候状态的转换其实承载到了链接上。

不要小看这个链接,当使用 spa 时,点击链接实际上就得变成用户交互这一种 action 而不是跳转到新的页面,本来我们的 js 只需要管理一个页面的 状态,但 spa 后,我们的 js 就需要管理整个系统的状态了。后端路由交给了前端,每点击一下链接再也不是 新返回一个 html,而是 继续让前端响应并管理整个状态机。其实这也是 C/S 架构 和 B/S 架构的巨大区别,而对于 spa 系统就和传统客户端开发,将切换页面当做 action 都是在客户端进行状态管理,这时 spa 系统就和传统客户端 没什么太大区别了,这句话不是我臆测出来的,对于我参与开发过的 electron 客户端应用其实就是一个 spa 系统,据我所知大部分的 electron 客户端都是 spa 的,所以 spa 系统其实更像是 C/S 架构。

退一步来说,实际上对于系统级别的状态机,对于 B/S 架构,是通过 浏览器 + 服务器端一起管理的,而 spa 这是将后端的活前移到了前端。而且你要知道,更重要的是,在传统前端 js 开发里可没有 浏览器请求 html 并解析 html 的功能、数据层管理,无论是新的概念的引入,还是(在某种意义上)完成浏览器的活,对于相同应用,spa 是比 传统 B/S 架构更复杂。


但更复杂难道就意味着不做吗,那就不对了。一切还是要按照业务需求来,技术是服务于业务的。spa 意味着更好的性能,而且我们不一定是要做整站整个系统的 spa,我们可以做局部 spa。这样用户点击链接的操作就不需要重新走一遍 浏览器输入 url 的整个操作(我称之为"刷页面"),而直接交给我们的 js,这样就不会有“刷页面”这种交互体验不好的情况,这种不“刷页面”的需求在对于性能、体验要求更高的移动端尤为重要,对于某些 pc 页面也同样有这样的需求。

编辑于 2017-04-24

文章被以下专栏收录