本文从属于笔者的Web 前端入门与最佳实践,2016年度版本在:2016-我的前端之路:工具化与工程化
撰写本文的时候笔者阅读了以下文章,不可避免的会借鉴或者引用其中的一些观点与文字,若有冒犯,请随时告知。文列如下:
RePractise前端篇: 前端演进史
前端的变革
致我们终将组件化的Web
我感觉到的前端变化
解读2015之前端篇:工业时代 野蛮发展
前端工程化知识要点回顾&思考
Thoughts about React, Redux & javascript in 2016
如果你想进行WebAPP的学习,建议先看下我的编程之路:知识管理与知识体系相关内容
顺便推广下笔者总结的泛前端知识点纲要总结:Coder Essential之客户端知识索引(iOS/Android/Web) 、Coder Essential之编程语言学习知识点纲要、Coder Essential之编程语言语法特性概论
几年前初入大学,才识编程的时候,崇尚的是一路向下,那个时候喜欢搞Windows、Linux底层相关的东西,觉得那些做网页的太Low了。直到后来偶然的机会接触到HTML、JavaScript、CSS,很长一段时间觉得这种这么不严谨的,毫无工程美学的搭配不过是诗余罢了。后来,深入了,才发现,能够有幸在这片星辰大海里游荡,可以以几乎领先于其他方向的技术变革速度来感受这个时代的脉动,是多么幸运的一件事。这是一个最坏的时代,因为一不小心就发现自己Out了;这也是一个最好的时代,我们永远在前行。繁华渐欲,万马齐鸣!
借用苏宁前端结构师的总结,任何一个编程生态都会经历三个阶段,第一个是原始时期,由于需要在语言与基础的API上进行扩充,这个阶段会催生大量的Tools。第二个阶段,随着做的东西的复杂化,需要更多的组织,会引入大量的设计模式啊,架构模式的概念,这个阶段会催生大量的Frameworks。第三个阶段,随着需求的进一步复杂与团队的扩充,就进入了工程化的阶段,各类分层MVC,MVP,MVVM之类,可视化开发,自动化测试,团队协同系统。这个阶段会出现大量的小而美的Library。当然,笔者以Tools-Frameworks-Library只是想说明我个人感觉的变化。
笔者从jQuery时代一路走来,经历了以BootStrap为代表的基于jQuery的插件式框架与CSS框架的兴起,到后面以Angular 1为代表的MVVM框架,以及到现在以React为代表的组件式框架的兴起。从最初的认为前端就是切页面,加上一些交互特效,到后面形成一个完整的webapp,总体的变革上,笔者以为有以下几点:
移动优先与响应式开发
前端组件化与工程化的变革
从直接操作Dom节点转向以状态/数据流为中心
笔者在本文中的叙事方式是按照自己的认知过程,夹杂了大量个人主观的感受,看看就好,不一定要当真,毕竟我菜。梳理来说,有以下几条线:
交互角度的从PC端为中心到Mobile First
架构角度的从以DOM为中心到MVVM/MVP到以数据/状态为驱动。
工程角度的从随意化到模块化到组件化。
工具角度的从人工到Grunt/Gulp到Webpack/Browserify。
在正文之前,重要的事情说三遍,我是菜鸟!我是菜鸟!我是菜鸟!从来都没有最好的技术,而只有合适的技术与懂它的人。我感谢这些伟大的类库/框架,感恩它们的Contributor,给我呈现了一个何其广阔的世界。虽然2015的前端领域有点野蛮生长,但是也体现了前端一直是开源领域的扛鼎之处,希望有一天我也能为它的繁荣做出自己的贡献。
基石与催化剂
浏览器的跃进
现在H5已经成为了一个符号,基本上所有具有绚丽界面或者交互的Web界面,无论是PC还是Mobile端,都被称为基于H5。笔者一直认为,H5技术的发展以及带来的一系列前端的变革,都离不开现代浏览器的发展与以IE为典型代表的老的浏览器的消逝。目前浏览器的市场分布可以由如下两个图:
这里顺嘴说下,如果想要明确某个属性是否可以使用可以参考Can I Use。话说虽然微信内置的某X5内核浏览器连Flexbox都不支持,不过它帮我们屏蔽了大量手机的底层差异,笔者还是非常感恩的。当然了,在有了Webpack之后,用Flexbox不是问题,可以查看这嘎达。
ECMAScript
2015年是JavaScript诞生的20周年。同时又是ES6标准落地的一年。ES6是迄今为止 ECMAScript标准最大的变革(如果不算上胎死腹中的ES4的话),带来了一系列令开发者兴奋的新特性。从目前es的进化速度来看,es后面应该会变成一个个的feature发布而不是像以前那样大版本号的方式,所以现在官方也在推荐 ES+年份这种叫法而不是 ES+版本。在ES2015中,笔者觉得比较欣赏的特性如下,其他完整的特性介绍可以参考这篇文章ES6 Overview in 350 Bullet Points。
Module & Module Loader:ES2015中加入的原生模块机制支持可谓是意义最重大的feature了,且不说目前市面上五花八门的module/loader库,各种不同实现机制互不兼容也就罢了(其实这也是非常大的问题),关键是那些模块定义/装载语法都丑到爆炸,但是这也是无奈之举,在没有语言级别的支持下,js只能做到这一步,正所谓巧妇难为无米之炊。ES2016中的Module机制借鉴自 CommonJS,同时又提供了更优雅的关键字及语法(虽然也存在一些问题)。
Class:准确来说class关键字只是一个js里构造函数的语法糖而已,跟直接function写法无本质区别。只不过有了Class的原生支持后,js的面向对象机制有了更多的可能性,比如衍生的extends关键字(虽然也只是语法糖)。
Promise & Reflect API:Promise的诞生其实已经有几十年了,它被纳入ES规范最大意义在于,它将市面上各种异步实现库的最佳实践都标准化了。至于Reflect API,它让js历史上第一次具备了元编程能力,这一特性足以让开发者们脑洞大开。
除此之外,ES2016的相关草案也已经确定了一大部分其他new features。这里提两个我比较感兴趣的new feature:
async/await:协程。ES2016中 async/await 实际是对Generator&Promise的上层封装,几乎同步的写法写异步比Promise更优雅更简单,非常值得期待。
decorator:装饰器,其实等同于Java里面的注解。注解机制对于大型应用的开发的作用想必不用我过多赘述了。用过的同学都说好。
更让人兴奋的是,JavaScript慢慢不再局限于前端开发中,NodeJs的提出让人们感受到了利用JavaScript进行全栈开发的能力,从此大大提高了开发的效率(至少不用多学习一门语言)。JavaScript在物联网中的应用也曾经引起一些追捧与风潮,不过今年物联网社区更加冷静地看待着这个问题,但是并不影响各大厂商对于JavaScript的支持,可以参阅javascript-beyond-the-web-in-2015这篇文章。笔者还是很看好JavaScript在其他领域继续大放异彩,毕竟ECMAScript 6,7已经是如此的优秀。
WebAssembly
WebAssembly 选择了跟ES2015在同一天发布,其项目领头人是大名鼎鼎的js之父Brendan Eich。WebAssembly旨在解决js作为解释性语言的先天性能缺陷,试图通过在浏览器底层加入编译机制从而提高js性能。WebAssembly所做的正是为Web打造一套专用的字节码,这项标准在未来应用场景可能是这样的:
开发应用,但使用任何一门可被编译为WebAssembly的语言编写源代码。
用编译器将源代码转换为WebAssembly字节码,也可按需转换为汇编代码。
在浏览器中加载字节码并运行。
需要注意的是,WebAssembly不会替代JavaScript。越来越多的语言和平台想在Web上大展手脚,这会迫使JavaScript和浏览器厂商不得不加快步伐来补充缺失的功能,其中某些功能通过复杂的JavaScript语义来实现并不合适,所以WebAssembly可以作为JavaScript的补集加入到Web阵营中来。WebAssembly最一开始的设计初衷就是作为不依赖于JavaScript的编译目标而存在,进而获得了主流浏览器厂商的广泛支持。很期待有一天WebAssembly能够发展起来,到那个时候,我们用JavaScript编写的应用也会像现在用汇编语言写出的大型程序的感觉咯~
渐隐的jQuery与服务端渲染
HTML:附庸之徒
前端用于数据展示
在笔者最早接触前端的时候,那个时候还不知道前端这个概念,只是知道HTML文件可以在浏览器中显示。彼时连GET/POST/AJAX这些概念都不甚明了,还记得那个时候看到一本厚厚的AJAX实战手册不明觉厉。笔者阅读过Roy Thomas Fielding博士的Architectural Styles andthe Design of Network-based Software Architectures这篇论文,也就是RESTful架构风格的源处。在这篇文章里,笔者反而感觉最有感触的是从BS到CS架构的跃迁。一开始我觉得网页是典型的BS的,咋说呢,就是网页是数据、模板与样式的混合,即以经典的APS.NET、PHP与JSP为例,是由服务端的模板提供一系列的标签完成从业务逻辑代码到页面的流动。所以,前端只是用来展示数据。
那个时候笔者更菜,对于CSS、JS都不甚明了,一切的数据渲染都是放在服务端完成的。笔者第一次学HTML的时候,惊呆了,卧槽,这能算上一门语言嘛?太简单了吧。。。原来做个网页这么简单啊,然后生活就华丽丽打了脸。那个时候,根本不会以script
或者link
的方式将资源载入,而是全部写在一个文件里,好吧,那时候连jQuery都不会用。记得那个时候Ajax都是自己手写的,长长的毫无美感的大量重复冗余的代码真是日了狗。
为什么说HTML只是附庸之徒呢,那个时候我们没有把Browser的地位与其他的Client并列,换言之,在经典的Spring MVC框架里,如下所示,用户所有的逻辑操作的核心我们都会放置到Java代码中,根本不会想到用JavaScript进行控制。另一个方面,因为没有AJAX的概念,导致了每次都是表单提交-后台判断-重新渲染这种方式。这样导致了每一个渲染出来的网页都是无状态的,换言之,网页是依赖于后端逻辑反应不同有不同的呈现,自身没有一个完整的状态管理。
AJAX与客户端开发
笔者最早的区分CS与BS架构,抽象来说,会认为CS是客户端与服务器之间的双向通信,而BS是客户端与服务端之间的单向通信。换言之,网页端本身也变成了有状态。从初始打开这个网页到最终关闭,网页本身也有了一套自己的状态,而拥有这种变化的状态的基础就是AJAX,即从单向通信变成了双向通信。图示如下:
渐隐的jQuery
jQuery作为了影响一代前端开发者的框架,是Tools的典型代表,它留下了璀璨的痕迹与无法磨灭的脚印。笔者在这里以jQuery作为一个符号,来代表以Dom节点的操作为核心的一代的前端开发风格。那个年代里,要插入数据或者更改数据,都是直接操作Dom节点,或者手工的构造Dom节点。譬如从服务端获得一个用户列表之后,会通过构造节点的方式将数据插入到Dom树中。
但是不得不承认,在未来相当长的一段时间内,jQuery并不会直接退出历史的舞台,笔者个人认为一个重要的原因就是现在仍然存在着很大比重的各式各样的基于jQuery的插件或者应用,对于崇尚拿来主义的我们,不可避免的会继续使用着它。
You-Dont-Need-jQuery
jQuery引领了一个辉煌的时代,但是随着技术的演进它也慢慢在很多项目中隐去。jQuery这个框架本身非常的优秀并且在不断的完善中,但是它本身的定位,作为早期的跨浏览器的工具类屏蔽层在今天这个浏览器API逐步统一并且完善的今天,逐渐不是那么关键。因此,笔者认为jQuery会逐渐隐去的原因可能为:
现代浏览器的发展与逐步统一的原生API
由于浏览器的历史原因,曾经的前端开发为了兼容不同浏览器怪癖,需要增加很多成本。jQuery 由于提供了非常易用的 API,屏蔽了浏览器差异,极大地提高了开发效率。这也导致很多前端只懂 jQuery。其实这几年浏览器更新很快,也借鉴了很多 jQuery 的 API,如 querySelector
,querySelectorAll
和 jQuery 选择器同样好用,而且性能更优。
前端由以DOM为中心到以数据/状态为中心
jQuery 代表着传统的以 DOM 为中心的开发模式,但现在复杂页面开发流行的是以 React 为代表的以数据/状态为中心的开发模式。应用复杂后,直接操作 DOM 意味着手动维护状态,当状态复杂后,变得不可控。React 以状态为中心,自动帮我们渲染出 DOM,同时通过高效的 DOM Diff 算法,也能保证性能。
不支持同构渲染与跨平台渲染
React Native中不支持jQuery。同构就是前后端运行同一份代码,后端也可以渲染出页面,这对 SEO 要求高的场景非常合适。由于 React 等流行框架天然支持,已经具有可行性。当我们在尝试把现有应用改成同构时,因为代码要运行在服务器端,但服务器端没有 DOM,所以引用 jQuery 就会报错。这也是要移除 jQuery 的迫切原因。同时不但要移除 jQuery,在很多场合也要避免直接操作 DOM。
性能缺陷
jQuery的性能已经不止一次被诟病了,在移动端兴起的初期,就出现了Zepto这样的轻量级框架,Angular 1也内置了jqlite这样的小工具。前端开发一般不需要考虑性能问题,但你想在性能上追求极致的话,一定要知道 jQuery 性能很差。原生 API 选择器相比 jQuery 丰富很多,如 document.getElementsByClassName
性能是 $(classSelector)
的 50 多倍!
说这么多,只是想在以后技术选型的时候,能有一个通盘考虑,毕竟,这是曾经的Best Love。
蛋疼的模块化与SPA
如果当时的移动网络速度可以更快的话,我想很多SPA框架就不存在了。
随着踩得坑越来越多与类似于Backbone、AngularJs这样的更加纯粹全面的客户端框架的兴起,Single Page Application流行了起来。至此,在网页开发领域也就完全变成了CS这种理念。至此之后,我们会考虑在前端进行更多的用户交互与状态管理,而不是一股脑的全部交给后台完成。特别是页面的切换与不同数据的呈现不再是需要用户进行页面的跳转,从而在弱网情况下使用户获得更好的体验与更少的流量浪费。与此同时,前端就变得更加的复杂化,我们也迫切的需要更加完善的代码分割与管理方案,于是,笔者开始尝试接触模块化的东西。笔者自RequireJs、SeaJs兴起以来一直关注,但是从未在实际项目中投入使用。额,第一次用这两个框架的时候,发现貌似需要对现有的代码或者喜欢的jQuery Plugins进行封装,当时我这种懒人就有点心理阴影了。不过SeaJs作为早期国人开发的有一定影响力的前端辅助工具,笔者还是非常敬佩的。
前端扫盲-之打造一个自动化的前端项目
模块化的进步与不足
在笔者知道模块化这个概念之前,文件夹是这么分的:
看上去非常的工整,但是稍微有个多人协作的项目,或者稍微多用一点jQuery的插件,看着那十来二十个不知道里面到底是啥的JS文件,笔者是崩溃的。笔者最早打算使用模块化的动力来源于避免作用域污染,那个时候经常发现的问题是一不小心引进来的两个第三方文件就打架了,你还不知道怎么去修改。
模块一般指能够独立拆分且通用的代码单元,在ES6正式出来规范之前,我们会选择使用RequireJs或者SeaJs来进行有点像依赖注入的东西:
require([
'Tmpl!../tmpl/list.html','lib/qqapi','module/position','module/refresh','module/page','module/net'
], function(listTmpl, QQapi, Position, Refresh, Page, NET){
大概是这样子的,但是笔者就是觉得好烦啊,并且它整个页面的逻辑还是面向过程编码的。换言之,我如果页面上稍微换了个布局或者有那么三四个有交集的页面,那就日了狗了,根本谈不上复用。
Backbone.js:MVC方式的SPA
Backbone是笔者较早期接触到的,以数据为驱动的一种框架。Backbone诞生于2010年,和响应式设计出现在同一个年代里,但他们似乎在同一个时代里火了起来。如果CSS3早点流行开来,似乎就没有Backbone啥事了。不过移动网络还是限制了响应式的流行,只是在今天这些都有所变化。换言之,就是将数据的处理与页面的渲染分离了出来。算是在以jQuery那种以DOM操作为核心的基础上完成了一次变革。同样的笔者用过的框架还有easy-ui
,不过它是一个封装的更加完全的框架。开发时,不需要考虑怎么去写大量的HTML/CSS代码,只需要在他的组件内填充不同的逻辑与配置即可。很方便,也很不方便,记得笔者想稍稍修改下他的表格的功能都蛋疼了好一阵子。
Backbone相对而言会更开放一点,在笔者大量使用Angular的时候也有同学提议使用Backbone + avaon这种更轻量级的方案。我们用Ajax向后台请求API,然后Mustache Render出来,这里已经会开始将Web端视作一个完整的Client而不仅仅是个附庸的存在。一个典型的Backbone组件的代码如下:
//《前端篇:前端演进史》
define([
'zepto',
'underscore',
'mustache',
'js/ProductsView',
'json!/configure.json',
'text!/templates/blog_details.html',
'js/renderBlog'
],function($, _, Mustache, ProductsView, configure, blogDetailsTemplate, GetBlog){
var BlogDetailsView = Backbone.View.extend ({
el: $("#content"),
initialize: function () {
this.params = '#content';
},
getBlog: function(slug) {
var getblog = new GetBlog(this.params, configure['blogPostUrl'] + slug, blogDetailsTemplate);
getblog.renderBlog();
}
});
return BlogDetailsView;
});
可以看见,在Backbone中已经将DOM元素与数据渲染以及逻辑剥离了开来,这样就有助于进行团队内的分工与协作,以及大量的代码复用。那个时候经常会将Backbone与Angular进行对比,二者各有优劣。Backbone在显示模板、创建数据绑定和连接组件方面给使用者更多的选择。与之相反,Angular为这些问题提供了规定的方案,不过在创建模型与控制器方面的限制就比较少一些。笔者当时是因为想要用一套Framework来解决问题,所以还是投入了Angular的怀抱。
AngularJs 1.0:MVVM 方式的 SPA
AngularJs是第一个我真正喜欢的Framework,不仅仅是因为它提出的MVVM的概念,还有因为它自带的DI以及模块化的组织方式。或许正是因为使用了AngularJs 1.0,笔者才没有深入使用RequireJs、SeaJs这些吧。AngularJs 1.0的优秀与槽点就不细说了,在那个时代他成功让笔者有了一点完整的前端项目的概念,而不是多个分离的互相之间跳转的HTML文件。最近,AngularJs 2.0终于出了Beta版本,笔者也一直保持关注。不过个人感觉唱衰的声音还是会大于褒扬之声,从笔者个人感觉而言,一个大而全的框架可能不如多个小而美的框架更加的灵活,关于这个对比可以参考下文的Web Components VS Reactive Components
这一章节。此外,对于AngularJs 中一直诟病的性能问题,Facebook提出的Virtual DOM的算法毫无疑问为前端的性能优化指明了一条新的道路,笔者这里推荐一个Performance Benchmarks,其中详细对比了多个DOM操作的库。笔者在这里只贴一张图,别的可以去原文查看:
总体而言,Vue偏轻量,适合移动端,ng适应pc端,avalon适合兼容老浏览器的项目。虽然Vue.js现在也有组件化的实现,包括类似于Flux的Vuex这样的Single State Tree的框架,但是笔者还是比较倾向于把它当做一个MVVM模型来对待。
组件化的未来与Mobile-First
最初随着React的风靡,组件化的概念深入人心。笔者一直坚信组件化是非常值得去做的事情,它在工程上会大大提升项目的可维护性及拓展性,同时会带来一些代码可复用的附加效果。但这里要强调的一点是,组件化的指导策略一定是分治而不是复用,分治的目的是为了使得组件之间解耦跟正交,从而提高可维护性及多人协同开发效率。如果以复用为指导原则那么组件最后一定会发展到一个配置繁杂代码臃肿的状态。组件化最著名的标准无疑是W3C制定的Web Components标准,它主要包含以下几个方面:
模板能力
ShadowDom 封装组件独立的内部结构
自定义原生标签
imports解决组件间的依赖
不过这个标准本身还没发扬光大就被Angular、React这样的框架完爆了,不过他还是指明了我们组件化的几个准则:
资源高内聚:有点像Vue提到的理念,Single File Component。组件资源内部高内聚,组件资源由自身加载控制
作用域独立:内部结构密封,不与全局或其他组件产生影响
自定义标签:可以像使用HTML的预设标签一样方便地使用组件
可相互组合:组件正在强大的地方,组件间组装整合
接口规范化:组件接口有统一规范,或者是生命周期的管理
Web Components VS Reactive Components
对于Web组件化的典型代表,应该是React与Angular 2。Angular 2基本上完全革了Angular 1的命,Angular开发团队最早于2014年3月提出路线图,直到2015年底才进入alpha阶段。笔者自Angular 2开发之始就一直保持关注,见证了其规范或者接口的更迭。不可否认Angular 2在性能以及设计理念上都会比Angular 1先进很多,但是随着2014年中到2015年初以React为代表的组件式UI框架以及Flux/Redux为代表的响应式数据流驱动兴起,可能Angular 2并不会达到Angular 1的高度。笔者也在断断续续地更新一些Angular 2的指导与学习文档,不过确实,除了从零开始的大型项目,Angular 2还是太笨重了。
Will Angular 2 be a success? You bet!(注意,评论更精彩)
实际上,在我们选择一个库或者所谓的框架时,为我们的组件选择一个合适的抽象可能会比觉得哪个框架更好更有意义。目前Web的组件化开发分为两个大的趋势,一个是以Angular 2、Polymer为代表的Web Components,另一个是以React、Vue、Riot为代表的Reactive Components。目前Web Components方面因为各个库之间无法就如何定义它们达成一致,导致了类似于Angular 2、Aurelia这样的框架用它们自己的核心来定义Web Components。只有Polymer 100%实践了Web Components的规范。Web Components有点类似于Google,而React更像Facebook。
另外,当我们选择一个框架时,还需要考虑清楚我们是需要一个包含了所有的功能的固执己见的框架,就像Angular2、Ember 2这样的,还是一系列小的专精的框架的组合,就像React、Flux以及React Router这样的。当然,我们在选择一个框架时还必须考虑进它潜在的变化的代价与难度,以及与其他的技术集成的难度,还有就是他有没有一个完善的生态系统。
就像笔者在自己的[AARF]()提及的,无论前后端,在这样一样敏捷式开发与快速迭代地背景下,我们需要更多独立的分离的可以方便组合的类似于插件一样的模块。
响应式解决方案
随着WAP的出现与移动智能终端的飞速普及,开发者们不得不面临一个问题,大量的流量来自于手机端而不再是PC端,传统的PC端布局的网页,在手机上显示的根本不友好,什么鬼!最早的时候人们考虑的是面向PC端与WAP设计不同的页面,不过这样就毫无疑问将原来的工作量乘以二,并且产品管理与发布上也会存在着一定的问题,特别是在那个组件化与工程化理念还没有流行的时代里。于是,人们开始设计一套能够针对不同的屏幕响应式地自反馈的布局方案,也就是这里提到的响应式设计。
响应式设计不得不提到的一个缺点是:他只是将原本在模板层做的事,放到了样式(CSS)层来完成。复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。
笔者最早接触到的响应式设计来自于BootStrap,它的Media Query功能给当时的笔者很大的惊喜的感觉。特别是CSS3中Flexbox的提出,更是能方便地践行响应式设计的原则。不过,就以淘宝首页为例,如果用响应式方式完成一套代码在PC端与手机端不同的完全适应的展示效果,我觉得还不如直接写两套呢。不可否认响应式设计在例如菜单啊,瀑布流布局啊这些功能组件上起到了非常巧妙的作用,但是为了单纯的追寻响应式布局而把整个CSS的逻辑判断搞得那么复杂,那我是拒绝的。特别是现在组件化这么流行的今天,我宁可在根控件中自由的组织各个组件,也好过不断地自适应判断。
笔者不是非常提倡响应式解决方案来解决从PC端到移动端的迁移,笔者个人觉得PC端和移动端就是额,不是同一种画风的东西。话说笔者接触过不少完全用代码控制的响应式布局,譬如融云的Demo,它可以根据你显示器屏幕控制元素的显隐和事件。不可否认设计很精巧,但是在没有组件的那个时候,这种代码复杂度和性价比,在下服了。笔者在自己的实践中,对于纯移动端的响应式开发,譬如微信中的H5,还是比较喜欢使用pageResponse这种方式或者它的一些改进版本。
移动优先
响应式解决方案,代表着随着不同的分辨率下智能的响应式布局。而移动优先的概念,笔者认为则是在界面设计之初即考虑到适应移动端的布局。当然,还有一个方面就是要照顾到移动端的浏览器的语法支持度、它的流量以及各种各样的Polyfill。
Hybrid:WebView VS Cross Compilation
笔者很懒,最早的时候只是有一点Android开发经验,那个时候Hybrid技术刚刚兴起,天天看DZone上N多的炫耀自己的Hybrid开发多快、性能多好的文章,立马激发起了我的懒癌。写一波就能跨平台运行,多爽啊!Hybrid技术分为两个大的分支,一个以Cordova为代表的基于系统的WebView与本地调用。另一种早期以Titanium、Tamarin,如今以React Native这样为代表的Cross Compilation,即跨平台编译技术。
在我们需要学习C语言的时候,GCC就有了这样的跨平台编译。
在我们开发桌面应用的时候,QT就有了这样的跨平台能力。
在我们构建Web应用的时候,Java就有了这样的跨平台能力。
在我们需要开发跨平台应用的时候,Cordova就有了这样的跨平台能力。
于是乎,在笔者第一次正式创业时,我斩钉截铁的跟投资人说,用Hybrid开发,用Cordova,没错的。记得那时候笔者还不懂iOS开发,所以在第一次正式做App的时候选择了Ionic 1.0。其实最早是打算用jQuery Mobile,不过写了第一个小的tab的Demo然后在自己的千元机上运行的时候,打开应用竟然花了20多秒,当时投资人看到的时候脸是绿的,心是凉的。估计是那时候还不会用jQuery Mobile吧(虽然现在也不会),但确实不是一个可行方案。后来笔者转到了Ionic 1.0,确实一开始感觉不错,速度还阔以。但是当时笔者还小,犯了一个很大的认知错误,就是打算完全摒弃掉Native全部用Web技术开发,于是,一个简单地文件上传分分钟就教我做了人。最后产品做出来了,但是压根用不了。插一句,一开始为了在Android老版本设备上解决WebView的兼容性问题,打算用Crosswalk。笔者第一次用Crosswalk编译完成之后,吓尿了。速度上确实快了一点,但是包体上实在增加的太大了,臣妾做不到啊!至此之后,笔者熄灭了完全依赖于Cordova进行APP开发的理念。
结果时间轴又错了,人们总是超前一个时期做错了一个在未来是正确的决定。大概是那个时候机器性能还不是足够的好吧。
Cordova或者Webview这种方向是没错的,现在也大量的存在于笔者的APP中,但是对于中大型APP而言,如果直接架构在Cordova之上,笔者还是不推荐的。Build Once,Run Everywhere,貌似做不到了,或者说差强人意。那就考虑Learn Once,Write Everywhere。React Native又引领了一波时代潮流。
Cross Compilation的典型代表是NativeScript与React Native。笔者自然是更喜欢React Native的,毕竟背靠整个React生态圈,对于原生组件的支持度也是很好的。React框架本身虽好,但是还是有许多可以与之媲美的优秀的框架的,但是React依靠Virtual DOM以及组件化等概念,依赖Facebook工程师强大的工程与架构能力,已经打造了一个完整的生态。特别是0.14版本之后的react与react-dom的分割,愈发的可以看出React的雄心壮志。将表现层与具体的界面分离开来,通过Canvas、Native、Server乃至未来的Desktop这样不同的渲染引擎,保证了代码的高度重用性,特别是逻辑代码的重用性。
工程化与Builder
前端工程化
大部分时候我们谈论到工程化这个概念的时候,往往指的是工具化。但是任何一个通向工程化的道路上都不可避免的会走过一段工具化的道路。笔者最早的接触Java的时候用的是Eclipse,那个时候不懂什么构建工具,不懂发布与部署,每次要用类库都要把jar包拷贝到Libs目录下。以至于多人协作的时候经常出现依赖相互冲突的问题。后来学会了用Maven、Gradle、Jenkins这些构建和CI工具,慢慢的才形成了一套完整的工作流程。前端工程化的道路,目标就是希望能用工程化的方法规范构建和维护有效、实用和高质量的软件。
笔者个人感觉的工程化的要素,会有以下几个方面:
统一的开发规范(语法/流程/工程结构)与编译工具。实际上考虑到浏览器的差异性,本身我们在编写前端代码时,就等于在跨了N个“平台”。在早期没有编译工具的时候,我们需要依赖自己去判断浏览器版本(当然也可以用jQuery这样人家封装好的),然后根据不同的版本写大量的重复代码。最简单的例子,就是CSS的属性,需要加不同的譬如
-o-
、-moz-
这样的前缀。而这样开发时的判断无疑是浪费时间并且存在了大量的冗余代码。开发规范也是这样一个概念,JavaScript本身作为脚本语言,语法的严谨性一直比较欠缺,而各个公司都有自己的规范,就像当年要实现个类都有好几种写法,着实蛋疼。模块化/组件化开发。在一个真正的工程中,我们往往需要进行协作开发,之前往往是按照页面来划分,但是会造成大量的重复代码,并且维护起来会非常麻烦。这里的模块化/组件化开发的要素与上面的第一点都是属于开发需求。
统一的组件发布与仓库。笔者在使用Maven前后会有很大的一个对比感,没有一个统一的中央仓库与版本管理工具,简直就是一场灾难。这样也无法促进开发者之间的沟通与交流,会造成大量的重复造轮子的现象。
性能优化与项目部署。前端的错误追踪与调试在早期一直是个蛋疼的问题,笔者基本上每次都要大量的交互才能重现错误场景。另一方面,前端会存在着大量的图片或者其他资源,这些的发布啊命名啊也是个很蛋疼的问题。当我们在构建一个webapp的完整的流程时,我们需要一套自动化的代码质量检测方案来提高系统的可靠性,需要一套自动化以及高度适应的项目发布/部署方案来提高系统的伸缩性和灵活性。最后,我们需要减少冗余的接口、冗余的资源请求、提高缓存命中率,最终达到近乎极致的性能体验。
Webpack
Webpack跟browserify本质上都是module bundler,差异点在于Webpack提供更强大的loader机制让其更变得更加灵活。当然,Webpack的流行自然还是离不开背后的react 跟facebook。但是从现在HTTP/2标准的应用及实施进展来看,Webpack/browserify这种基于bundle的打包工具也面临着被历史车轮碾过的危机,相对的基于module loader的jspm反而更具前景。Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。相较于Webpack,Browserify具有更悠久的历史,记得当年还是看这篇文章才开始慢慢认识到Webpack,那时候Webpack还是一个相当年轻的框架啊。
Webpack是前端开发真正意义上成为了工程级别,而不再是随意,可以看看这篇文章。笔者第一次看Webpack的时候,没看懂。当时用Gulp用的正顺手,不需要自己往HTML文件里引入大量的Script文件,还能自动帮你给CSS加前后缀,自动地帮你压缩,多好啊。不过Grunt和Gulp现在存在的问题就是需要自己去组装大量的插件,参差不齐的插件质量导致了大量时间的浪费。并且Gulp/Grunt还并不能称为一个完整的编译工具,只是一个辅助工具。
Webpack还有很令笔者欣慰的一点,它支持Lazy Load Component,并且这种懒加载技术是与框架无关的。这样就避免了笔者在编码时还需要考虑固定的组件或者代码分割,毕竟在一个快速迭代的项目中还是很难在一开始就规划好全部的组件分割。这一点对于笔者这种被SPA JS加载以及原来的无论是基于Angular的懒加载还是React Router的懒加载折磨的人是一个大大的福音。同时,Webpack还支持配合了React Hot Loader的代码热插拔,可以大大地提高代码的开发效率。毕竟等着Browserify编译好也是很蛋疼的。
在笔者的个人的感触中,Webpack是促成了前端真正工程化的不可缺少的一环。记得之前看过美团的前端技术分享,它提出了前端分布式编译系统。大型系统的分布式编译很常见,但是在前端,这典型的脚本与解释执行的领域,出现了大型分布式编译系统,还是很让人震惊的。笔者是个懒惰的人,懒人总希望可以用一套方法去解决全部的问题,所以慢慢的笔者完全切入到了Webpack。或许未来某天也会离开Webpack,就像离开jQuery一样,但是会永远记得陪我走过的这些岁月。
响应式数据流驱动的页面
现代这样一个云计算与大数据的时代,Data Driven的概念早已深入人心。随着WEB应用变得越来越复杂,再加上node前后端分离越来越流行,那么对数据流动的控制就显得越发重要。笔者在开篇就提及过,前端变革的一个核心路线就是从以DOM Manipulation为核心到以State为核心,这样也就能将逻辑控制、渲染与交互给分离开来。用一个函数来表示,现在的渲染就是:$UI=f(state)$。在React中$f$可以看做是那个render函数,可以将state渲染成Virtual DOM,Virtual DOM再被React渲染成真正的DOM。在控制器中,我们不需要关心DOM是如何变更的,只需要在我们的业务逻辑中完成状态转变,React会自动将这个变更显示在UI中。其实在Angular中也是这样,只不过Angular中采取的数据双向绑定与脏检测的技术,而React中采用的是JSX这样来完成一种从状态到页面的绑定。
这样一种以响应式数据流驱动的页面,毫无疑问会将编程工作,特别是复杂的交互与逻辑处理变得更加明晰,也方面了产品迭代与变更,也就是敏捷式开发的理念。采用这样的响应式数据流驱动的方式,还有一个很大的好处就是方便错误追踪与调试。SPA State is hard to reproduce!
而在Redux这样的框架中,存在着类似于Global State Object这样的可以将页面全部还原,来重现Bug的东西。当测试人员/用户遇到问题的时候,主动将当时的State发送给开发人员,开发人员就阔以直接根据State来还原现场咯。Immutable的魅力正在于此,灵活的可追踪性。
Redux是在flux的基础上产生的,在此基础上它引入了函数式编程、单一数据源、不可变数据、中间件等概念,基本思想是保证数据的单向流动,同时便于控制、使用、测试。Redux不依赖于任意框架(库),只要subscribe相应框架(库)的内部方法,就可以使用该应用框架保证数据流动的一致性。Redux在一定程度上可以说是今年React生态甚至整个前端生态中影响最大的一个框架,它给整个前端技术栈引入了很多新成员,尽管这些概念可能在其他领域已经有了广泛的应用。笔者还是比较推崇响应式开发的,实际工作中用的比较多的还是FPR的一些实现,譬如RxJava啊这些。Redux标榜的是Immutable的State Tree,而Vue采用的是Mutable的State Tree。
笔者在不长的代码之路上从Windows Developer 到 Pentester,到 Android Developer,到 Server-Side Developer,最后选择了Front-end 作为自己的归宿。不过Server-Side Architecture 和 Data Science也是我的最爱,哈哈哈哈哈哈,怎么有一种坐拥后宫的赶脚~