2015-我的前端之路
本文从属于笔者的Web 前端入门与最佳实践,2016 年度版本在:2016-我的前端之路:工具化与工程化
撰写本文的时候笔者阅读了以下文章,不可避免的会借鉴或者引用其中的一些观点与文字,若有冒犯,请随时告知。文列如下:
如果你想进行 WebAPP 的学习,建议先看下我的编程之路:知识管理与知识体系相关内容
顺便推广下笔者总结的泛前端知识点纲要总结:Coder Essential 之客户端知识索引(iOS/Android/Web) 、Coder Essential 之编程语言学习知识点纲要、Coder Essential 之编程语言语法特性概论
2015-我的前端之路:数据流驱动的界面
几年前初入大学,才识编程的时候,崇尚的是一路向下,那个时候喜欢搞 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 节点。譬如从服务端获得一个用户列表之后,会通过构造<i>
节点的方式将数据插入到 Dom 树中。
但是不得不承认,在未来相当长的一段时间内,jQuery 并不会直接退出历史的舞台,笔者个人认为一个重要的原因就是现在仍然存在着很大比重的各式各样的基于 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 标准,它主要包含以下几个方面:
-
<template>
模板能力 -
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 也是我的最爱,哈哈哈哈哈哈,怎么有一种坐拥后宫的赶脚~
希望能永远在这条路上,心怀激情,热泪盈眶。