十年来感受的前端技术变化

403 查看

07年底,我所在的团队需要重构一个产品,在此之前,我们的前端框架是这样的:

  • 使用HTML Components(htc)作为基础控件的实现方式,包括选项卡,树形表格,日期选择等控件。
  • 在原生js的基础上作了一些简单封装,形成了包括表单校验,弹出菜单(基于popup),简单图表(基于XML),动态表单等功能的业务公共库。
  • 使用XMLHTTP作为前后端通信方式,将请求参数序列化为XML发送给服务端,反序列化之后,反射调用后端服务(Java和.net),再把返回结果序列化为XML传输回来,用JS解析为JavaScript对象。这个传输方式从03年版本就开始使用,还在AJAX概念出现之前,只是一直使用的是同步传输方式,调用的时候界面会卡死。
  • 开发方式也是前后端分离的,前端只写HTML和JS,后端提供接口(但并不是HTTP接口,而是服务端的类接口,然后通过一个统一的facade类去反射调用)。

这个时期的版本不用说,肯定都是只支持IE的,我们当时需要兼容的浏览器包括IE 5.0,5.5,6,后来7出来之后还需要支持7。

这个产品重构的目的是,对近几年积累的业务需求进行整合,并且把服务端完全迁移到Java。对于前端来说,其实不做迁移也可以,但当时我们发现一个问题,FireFox这个东西突然崛起了,所以,我们从原先面临的只支持IE,变成了可能要支持跨浏览器。

所以我们觉得需要把这个事情做一下,因为当时判断,IE的份额可能会下降到70%左右,FireFox可能会占有25-30%的份额,这个兼容是有可能需要的,虽然以我们的场景,几乎全部面向行业用户,可以把浏览器限制得很死,但也有部分用户自服务的产品,将来还有扩大的趋势。

这时候我们问题就很大了,因为前端的基础功能面临大改,一些校验之类的库好办,通信封装也好办,基于htc的控件就是个大问题了,必须全部重写。

当时几个成员产生了热烈的讨论,jQuery那时候还没有一家独大,也没有产生这种趋势,可选项包含:

  • ExtJS这样的全整合框架
  • jQuery,prototype,mootools,Ext Core这样的核心js库加外围

在这两个选择里,我们排除了第一个,因为虽然看上去它很符合我们的业务场景,但我们的定制化需求比较多,不确定有能力在这个基础上做定制,可控性不好。

所以我们就决定选一个核心js库,然后自己开发外围控件。那么,选谁呢?最终选的是prototype,因为大家判断,我们不需要类似class的机制,这样就把后面两个排除了,而我们不需要太多DOM方面的封装,因为最后业务上需要直接操作DOM的东西不会很多,都会被我们封装掉,所以又把jQuery排除了。

所以我们的需求其实也很明确,就是有一定基础功能的js核心库。然后就是在这个基础上开发控件了,时间也很仓促,大约只有2-3个人,2-3个月,最后几个东西:

  • 数据表格
  • 树形表格
  • 日期选择(我们的日历需求比较变态,因为有伊朗和尼泊尔客户,所以会有波斯日历和尼泊尔日历之类。。。)
  • 分页
  • 动态表单
  • 国际化方案
  • 其他一些基础功能,布局指引之类

最后,因为赶时间,这个版本的框架最终并未跨浏览器,部分控件的功能还是使用了IE only的特性……更致命的是,我们因为没时间,所以在业务开发指引上花的时间很少,这导致业务开发人员赶工的时候,整个就不可控了,因为我们后面还参与了业务开发,十多个写HTML和JS的人,被业务压着走,压根没人有时间看FireFox的状况……

后面几年中,这个大版本被作为基础版本,拉了无数分支出来,期间,面临了IE8之类的兼容性修改,除此之外,已经没有机会再修改基础库的部分了。

09年底,我主导这个产品下一代版本的前端框架选型。我们现在回头看这个时间点,会发现很尴尬,当时流行的,或者即将流行的所有前端方案,其实也都是第一代的理念,在现在这个时候,都已经被时代大潮冲刷得死伤殆尽了。

所以我当时非常纠结,我是预见到前端这几年的乱象的,当时大家炒得最火的概念是什么,是HTML5,然而我看了这方面的一些资料,发现广义上,更多提供的是一些功能方面的东西,或者能提升布局方式,等等,这与我们想要的东西相去甚远,好比说,我们想要蒸汽机之类的东西,发现手里将要有的还只是各种铁块。

我当时想要什么呢?想想我当时有过什么,经历过什么,早在05年初,我就有了一种设想,那就是前端的全组件化开发,当时的老大问我对现有项目有什么看法,我提出了一个方案:

  • 扩展htc的使用场景,不限于基础组件,把业务界面也全部组件化
  • 各组件可以独立通过XMLHTTP与服务端交互,也就是说,你把一个已经调试好的组件加到界面之后,什么都不用管,它自己是能够管理与服务端的通讯的,然后你只要管跟它怎么交互就行了
  • 在此基础上,考虑界面的动态定制,像积木一样拼装业务界面

这个想法在当时确实比较激进,所以当然没人支持。但我在05-09这几年的开发过程中,目睹各种业务代码的混乱,觉得可能还是应该推进组件化的开发理念。理念是有了,细节怎么处理呢,因为HTML Components这一规范被废弃之后,我发现现有的任何方案都不再能够像原先那样简便地封装组件了,如果对于业务开发人员来说,开发业务组件的代价过高,那工程成本更加不可控。

回想我们使用HTML Components的时候,开发一个组件易如反掌,就像开发普通页面一样简单,只需在头部声明对外的属性、方法、事件即可,样式也是隔离的,JS作用域也是隔离的,使用起来也是非常简单,就是一个自定义标签,而且是客户端的(区别于taglib之类的服务端标签封装)。

以05年时候那个产品的场景而言,绝大部分界面都是普通的配置和管理,每个界面的独立性都较高,并不存在强集成的场景,所以是否使用组件化方式开发,并不是很重要,这跟我前几天在微博上说:“轻量级管理控制台有至少100种做法”是一个意思。

但是后来的做的,有包括CRM和呼叫中心之类的强集成产品,不再是原先那种单菜单配置页面了,整个产品几乎就是一个菜单,然后通过各种交互去触发一系列功能,在这种场景下,组件化就变成了一个重要的实施手段。

所以我万般无奈,就把目光投到04年开始持续关注的一项技术:Adobe Flex,在这个东西刚出来的时候,我曾经预言微软会推出一个精简的CLR,作为浏览器插件运行,与Flash平台抗衡,后来大家看到了SilverLight,不过这个东西当时的普及度并不好,第一代极其简陋,第二代稍微好一点,所以我没有考虑它。

在2009年,Adobe Flex算是比较成熟了,带有完善的组件化机制,生命周期管理,强大而易于扩展的基础控件,优雅而强大的开发语言,但我当时有判断,这东西是一个走下坡路的平台,而且Adobe自身有很大不足,也没有能力把它支撑和运营好。最终,我反复权衡,仍然选择了使用它来构建这个版本。

所以当时我面临的压力非常大,公司内部的辩论达到白热化,正反双方都有相当多的人参与,而且反对者还略多些,年轻开发者居多。在有资格参与这项决策的各基层管理者和技术专家组的投票来看,双方也是基本对等的。这个争论现在回头看,很有意思,双方考虑的事情其实不在一个层面上,从基层开发者的角度,他会从流行趋势方面进行一个判断,觉得Flash必死,HTML永生,你让我1948年加入国民党,是何居心?

但从我们有些老员工的角度,看到的问题是由于公司开发人员水平参差不齐,导致很多代码写得非常糟糕,很难调试,也很难重构,更没有一些全局视图,让我们能看到代码的结构是怎样,数据的流动又是怎样,产品的质量如何,我们觉得对于大部分低层次开发者来说,JavaScript的约束还是太弱了,这样松散的语言在他们手里会造成开发效率极低,bug率很高。

当时我有个比喻,我们相当于在2000年选择了使用Delphi。这个比喻是为了向公司高层说明,我们到底是在干什么,大家在这么激烈地吵什么。因为他们虽然不一定熟悉当时的技术方案,但都是从2000年那个时代过来的,对Delphi的盛衰还是很熟悉的。

最终我们的判断是:5年之内,泛HTML体系的组件化方案不会有一个相对稳定的选择,我们是做企业软件产品的,并不害怕使用的技术相对过时,怕的是时常有剧烈变动,而且不能掌控。所以,打算用Adobe Flex这样的东西,来做一个5-8年的支撑,在这段时期内,见机行事,在泛HTML体系里作一些探索,跟进可能会流行的技术,并且加以积累。另外,选择一种轻量级的解决方案,用于构建面向个人用户的门户,这条线选择的是jQuery和BootStrap。

当时在轻量级这条线的解决方案上,我也有一些意见,因为在我们当时的开发团队中,大家对“控件”这个东西的依赖程度太高了,绝大部分人压根不具备我们现在所说的前端工程师的基本能力,只有调用控件的能力。而我认为,轻量级的场景中,应当严格控制“控件”的使用,绝大部分场景都是用基本样式加一些DOM操作就可以做到,当时我们的轻量场景是面向运营商的用户们,他们登录上去,查询话费,办理业务,兑换积分,购买一些服务或者礼品之类。

所以,当我后来发现这样的场景也引入了上M的js库的时候,我的心情是非常崩溃的,后来有的团队考虑在这种业务场景下封装一些服务端标签,这个事情我并不赞同。很多时候,我们需要去使用服务端模板之类的方式生成界面,为的是页面性能优化之类,但我们所在的场景,并不需要这么苛刻的优化,页面DOM结构是比较简单的,反倒是JS这块需要严格控制。这么轻量级的场景,做服务端组件化的必要性也不是很大,只需引入js的模板库就可以了。

回头再看Flex这头的状况,我们用这个东西针对重量级的管理系统进行开发,但令人痛苦的是,绝大部分开发人员很难理解“组件化”这么一件事情,比如说,仍然保留“页面”这个称呼,越是老开发人员越是难改变思维,我有一次很激动地说:我们这种模式下,哪里来的页面?我们做的是一个软件系统,你可以把它理解为运行在浏览器中的桌面软件,只存在组件,不存在页面,页面是HTML体系中特有的称呼。

所以,做一个全业务组件化的实践,需要一次又一次从理念上去向业务团队灌输,什么是组件,组件树是怎样的,组件跟数据层如何通信,组件之间如何通信等等,如何提取合适的组件,不把这些理念灌输下去,整个产品也很难成功。

举例来说,之前系统规划人员增加了新模块的时候,一般是把数据库表结构截图发出来,然后最多画个草图表示界面,但在组件化的开发方式中,这中间至少还有一步组件规划、整合的过程,这一步应当需要严谨的考虑。

我所在的基础技术团队中,也有不少人对我们要面临的问题看得不太清楚,把责任想得太轻。之前那种简单的管理页面,只需要基础技术团队提供基础组件和公共库,公共样式的开发,但在全组件化的实践中,做完这些事情也才完成了整个事情的30%。

除了这些,还需要对业务团队培训组件化的相关理念,需要跟踪他们的开发过程,评审、观察、纠偏,还需要构建组件的管控、测试平台,否则,仍然是一种山寨的开发流程,而享受不到组件化所应当拥有的流水化生产体验。这一步其实没有做下去,投入还是太少了。

这个大产品版本以及附属项目一共有好几十名开发人员参与,历时2年多,虽然离我心目中的目标还有不少差距,但在某些方面的提升是能够感受得出来的,比如说开发效率的提升。除此之外,这个版本的美观程度比之前所有版本都好,终于有现代气息了,谁说企业用户就都是土鳖的,甚至有一次还收到过外籍市场人员专门的邮件夸赞,这让我们感到很振奋。

开发效率的提升另一方面还源自Flex自身的特点,独立于浏览器的插件,可以说,大部分前端开发人员都会面临跨浏览器的折腾,尤其是那几年,乱得可怕,兼容问题会把人折磨死,我们面对的客户,从欧美到亚非拉,各种低端高端浏览器并存,所以我们是绕开了这个问题,而这么复杂的组件界面,如果使用传统Web技术,在IE6、7等浏览器下的性能会惨不忍睹,基于插件体系的另外一个优点是,性能不受低端浏览器的制约,下限比较高。

在这个阶段,我们实际上是做了比较取巧的选择,但从长期角度看,还是必须回到泛HTML体系的。所以,从12年开始,我就开始考虑未来的产品技术选型。

在12年这个点上,我们能看到的东西还是比较多的,至少比几年前有了很明显的发展,比如说,jQuery终于一家独大了,比如说,AMD和CMD之类js模块封装机制的出现,比如说,Backbone,Knockout,Angular分别出现了不少使用者,比如说,ES6和Web Components规范的落地快看到曙光了。

但实事求是地说,这些所有东西加起来,开发体验还是比不过Adobe Flex,在重量级场景下,泛HTML体系的整合力度还是比较弱,对开发人员的技能要求也会高一些。我也关注Dart,关注TypeScript,(为什么不关注CoffeeScript,因为所在公司几乎全是只懂Java的开发人员,跟Coffee的理念差别太大,推广成本极高)。

感慨归感慨,选型还是一直要做,只是这个选型在实际有项目应用前,一直还会处于动态调整中。

在这段时间中,我们考察了Backbone,Knockout和Angular,也持续关注了司徒正美的Avalon,最终还是比较倾向于Angular的。

倾向于Angular的主要原因是,开发团队经过磨合,已经逐步习惯了组件化的开发方式和数据绑定所带来的开发理念的改变,Angular在这方面与Flex比较接近,学习成本很低,而且那些稍微繁琐的配置,对于这些习惯了Java的人员来说,并非负担。

所以,当时我们的基础技术团队也花了不少时间来学习Angular,看源码,踩坑,期间,团队的大漠穷秋翻译出版了国内第一本Angular中文书籍。

我们研究Angular,还有另外一个原因。在基于Flex的这代产品逐步稳定之后,又实现了一个二次开发平台,从数据模型的动态定义(类似ORM),到规则、流程、服务的动态定义,再到界面的可视化配置,动态的数据绑定。这个平台的有些方面考虑是不够成熟的,尤其是动态界面这块,细节不展开了。

所谓的动态数据绑定,指什么呢,其实核心机制类似于Angular的这种绑定关系。在Flex体系中,数据绑定是通过Proxy机制实现的,对POJO有一定程度的封装,比如Object就封装为ObjectProxy,而Array封装为ArrayCollection。我们当时需要创建的是:基于单个用户(或者会话)的数据联动体系。

这个是什么意思呢,对于某个登录用户来说,他所可能拥有的全部数据结构是可预测的,绝大部分是平级关系,没有关联,部分存在联动关系,我们要做的就是把这些关系描述出来,用可视化的形式配置,挂接在ORM机制上作为外壳,并且提供给业务流程、业务规则和动态界面作为绑定数据源。

这个数据的定义和描述机制,用现有技术类比,就好像是Meteor或者Relay,但当时我们对有些东西没有考虑清楚。因为对于某个用户而言,他的所有数据结构与界面上的组件结构是无关的,这意味着,如果不把数据做拆解传递,一旦遇到组件树上的不同层级指向数据源同一位置之类的情况,就不好办。因为我们组件之间也是不共享数据的,类似现在的React。所以我们当时的问题就是数据中间层设计得不好。

以我们当时的设计,其实是适合Angular这种机制的,Angular里面,不同层级的组件之间,数据可以有穿透,比如说你在最顶层绑定了一个dataStore,在隔了很多级的叶子节点上仍然可以绑定到dataStore.a.b这样的数据,这个a.b数据路径可以与叶子节点在组件树上的路径毫无关系。

在这个过程中,我还是一直在尝试考虑基础框架与组件化业务开发的结合,包括整个组件和数据的规划流程,管控机制等,也写了几篇文章,现在回头看,有一些不成熟的地方,另外有些东西当时觉得有坑,但正好被近期出现的新技术填平了。

举例来说,当时我觉得,不管是AMD还是CMD,都是在JS模块自身代码中声明依赖项,如果是出现代码路径调整之类的情况,这些依赖项的变更就是个问题,所以我设想了一种类似npm的方式,用数据库集中存放依赖关系,模块自身的内容不维护这个关系,然后构建阶段生成出来。

今年看到Webpack,里面提到module,bundle,entry chunk之间的关系,感到已经差不多把我当时考虑的那些东西全部做到了,社区的力量真是强大啊。

在之前争论Flex方案的时候,反对方的有些观点很典型,比如:Flex代码编写之后还需要编译,JS的不用。但其实最近几年我们看到,Web应用的开发过程逐渐离不开构建环节,不可能再像十年前那样,写完代码之后,最多只做下压缩就上线了,那是一种很原始的生产方式,所以,从这个方面看,组件化、构建过程,这些都是现代富Web应用开发方案中必不可少的部分,已经逐渐被广泛接受了。最近两三年,业界对前端工程化的关注度也已经比以前高很多了,令人欣慰。

前面这些年,我们还有一个大的缺陷,那就是一直对异步编程模型关注太少了,在早期我们一直使用同步的XMLHTTP,后来到了用Flex的时候,改用异步的HTTP通信,使用AMF协议传输数据,我们的经验仍然停留在使用回调函数这种初级的方案上,间或使用事件,并不知道任何跟promise之类理念相关的东西,所以代码有不少很不优雅,这个跟眼界有关,还是缺少学习所致。

现在距离09年底已经整整6年过去了,回头看来,6年前可能接触到的任何前端领域的框架或者库,到今天都已经全部是过时的。这6年时间,我们可能积累出一些经验,但大部分东西都不可避免地失效了,这就是野蛮生长期的痛。

这6年里,我们看到AMD,CMD这些模块机制逐步流行,然后又突然衰落,Angular为代表的前端MV*框架横空出世,带给前端社区巨大的冲击,React挟组件化和Virtual DOM之力快速崛起,你方唱罢我登台,乱花渐欲迷人眼。

两年前我从之前公司离职,心中充满迷茫,未来的Web技术会怎样发展,重型Web应用应当采用怎样的整体方案去建造,自己并不能说出一个精确答案。今天我们再要考虑未来的Web应用技术选型,已经没有两年前那么迷茫了,这两年仍然在持续学习,持续思考,每次面临选型,还是如履薄冰,一再审视项目类型,人员状况。

未来的前端Web应用技术选型还有不小的变数,身为大龄前端技术人员,一方面感慨有些自己熟知的技术逐步落幕消亡,另外一方面又看到新事物不断出现,以种种方式改进和冲击着我们的开发方式。生在这个时代是一种不幸,也是幸运。毛主席教导我们:三天不学习,不如刘少奇。说到底,保持学习和思考,是现在这个阶段最该做的。旧技术虽然消亡了,但它们留下的思维启发永在。

后记:最近一直有种冲动,想把过去十年(2005-2015)时间所经历的一些事情总结一下,昨晚开始写,今天中午完善了一下,大致写完,意犹未尽,与大家共勉。