SPA vs. MPA
从字面上理解,SPA(单页面应用程序)整个应用只有一个页面,只加载一次Web静态资源,包括HTML+CSS+javascript,在导航过程中不需要重新加载渲染整个页面。而MPA恰恰相反,也就是每个页面都需要独立完整的从后端加载和渲染,早期的网站大多属于MPA。
那么SPA和MPA各自有哪些优缺点呢?
SPA第一次加载页面因为要加载很多静态资源,速度比较慢,但是之后前后端仅仅需要传递纯数据,页面渲染完全由前端完成,既节省了带宽,也提高了渲染速度。MPA则反之,用户的每次导航、提交表单都需要由后端生成一个完整的页面,前后端之间传递了大量重复类似的标记语言文本,通信效率较低。但是由于在首页加载速度上,MPA相对有优势,所以有些应用出于用户体验的考虑,结合server side render和SPA的混合方式。从系统架构角度考虑,SPA最重要的革新在于实现了前后端的解耦,Server端不必既要考虑业务逻辑又要考虑页面展示,只要聚焦于业务,将前端需要的数据开放出来即可。后端聚焦于数据之后,可以被多种前端所共享,比如Web应用,Mobile应用,甚至是AR/VR等,即所谓的大前端。对于Web前端,因为静态资源都已经加载,导航、渲染等界面逻辑都放在浏览器端,所以可以使用如chrome developer tool或者firebug等工具独立调试。NodeJS的出现,甚至让Web应用开发和业务开发从语言层面进行了完全解耦。
SPA和MPA各有优劣,虽说SPA面临SEO困难,易受XSS攻击等问题,但总体上说,优点多于缺点,问题正在解决,SPA已经成为Web 2.0时代的主流方式。
三驾马车
Web应用万变不离其宗,位于Web技术核心地位的仍旧是HTML、CSS、JavaScript三驾马车。
HTML5提供了更丰富的语义化标签、富媒体支持、离线存储、移动设备支持等特性,支撑了不断涌现的各种业务需求,比如视频网站、网页游戏等。CSS3提供了更加丰富且实用的规范,各种特效、变换等效果增强了网页的表现能力,媒体查询方便了网页适配多种终端。互联网时代,各种应用层出不穷,仅仅依靠HTML和CSS无法满足用户对网页交互和表现能力的需求,JavaScript成为浏览器端最主要的编程语言。
伴随着Web前端业务越来越复杂,开发、调试愈加困难,前端技术在开发维护效率、扩展性、健壮性等内外需求的驱动下出现了各种各样的技术。根据GitHub网站的统计,2017年Javascript相关的pull requests遥遥领先于其他语言,比第二名的Python和第三名的Java的总和还多。人说2017年最火爆的是AI,AI带来Python的空前繁荣,但即便如此又奈我何?当初,JavaScript之父Brendan Eich借鉴了C语言的语法、Java的内存管理、Scheme语言的函数式编程以及Self的原型链继承思想,仅仅花了10天就设计出这门语言,但仓促的设计导致各种问题饱受诟病,于是有人说JavaScript是一门天生残疾的语言。自1995年诞生之后JavaScript一直缓慢发展,目前最广泛支持的版本是2009年发布的ES5版本,经过了多年的潜心打磨,TC39作为JavaScript官方机构,终于于2015年发布了ES6。ES6做出了很多实质性的升级,比如块变量、class、native promise、模块化等。最近几年,Javascript的版本发布逐渐年度例行化,2016年发布了ES7,2017年发布了ES8,JavaScript正向着标准、高效、简洁、健壮的方向迈进。
前端技术栈
框架是什么?模式是什么?都是不得已而为之的事情。当家里的桌子上堆满了爆米花、手机、内衣、蟑螂药和洗手液的时候,似乎这时候我们应该发明出抽屉,把东西分门别类地归置一下。当用JavaScript手忙脚乱直接操作DOM的时候,JQuery诞生了;当前端代码变得复杂而有章可循时,新框架诞生了;当各种框架、库让人眼花缭乱,无所适从时,包管理、包加载被发明出来;当button、panel、chart做得越来越漂亮时,新的控件库产生了;当bug满地走,调试已经忙不过来的时候,测试框架出现了。框架和类库就是帮我们把一些重复的并且已经受过验证的模式,抽象到一个帮你设计好的封装当中,帮助我们去应对这些复杂的问题。前端生态是如此昌盛,以致于“前端年年框架出,各领风骚两三年”,没有选择是痛苦的,选择太多也痛苦,最痛苦的是用了一个以后却很快出来另一个更酷的!没有一篇文章能够囊括整个前端生态,只能基于主要业务场景,基于主流方案,有选择地做一些介绍和分析,即便这样,也难免挂一漏万。
下面,将前端技术分解为不同角度和层次来介绍。
* 前端语言
* JS工具库
* 前端MVC框架
* UI框架
* 模块化和打包
* 自动化构建
* 包管理
* 测试工具
* 前后端协议
1. 前端语言
从ES6开始,JavaScript语言本身的标准化和发展进入新阶段。除了一些语法糖,如胖箭头、includes方法、class关键字等,让JS代码更加清爽外,新的extend继承方式、块变量等特性的引入则让JS更加像一门“正常”的语言。异步操作似乎是JavaScript规范一直在孜孜解决的问题,从最早的回调函数,到Promise,到Generator函数,再到async,这些新特性一方面解决了回调地狱问题,另外一方面也让代码更加符合人类的串行思维方式。如果觉得Promise这种异步处理方式满足不了复杂的应用场景,可以考虑一下RxJS。引用官网的说法:RxJS是使用Observables的响应式编程的库,它使编写异步或基于回调的代码更容易。
尽管有些浏览器不支持ES5以后的JavaScript新特性,但有Babel在,尽管放手使用最新的ES特性。将Babel串到你的构建流程中,它会将新的特性转化为浏览器支持更好的ES5代码。
JavaScript的弱类型经常会导致一些bug,这时候可以使用Flow/JSHint等静态检查工具。为提高代码可读性,可以使用Prettier来美化代码。
TC39的长期不作为,让一批不甘被ES5束缚住手脚的“方言”涌现出来,或者叫flavor,如CoffeeScript、Dart、Elm、TypeScript、Reason等。这些语言的初衷或者是为弥补原生JavaScript语言特性的不足,将一些更高级的语法引入进来,或者直接采用一种更成熟的语法,然后编译为JavaScript。从目前发展来看,CoffeeScript、Dart已经衰落,而TypeScript、Reason正当红,这些语言中一些有价值的特性逐渐被ECMAScript纳入标准,但是是否会取代JavaScript或者多大程度上取代JavaScript还很难说。
上面的各种方言不管如何强大,最后还是要transpile到JavaScript,JS在浏览器端一路高歌猛进,独孤求败,似乎千秋大业已成。但在某个角落,一股反叛力量正在孕育,这就是由谷歌,微软,Mozilla,苹果等公司合作推出的WebAssembly!看看有多反动:“一种全新的跨浏览器Web中间表示层安全代码,为未来浏览器带来一种可执行的标准二进制数据格式,使得越来越多的开发者,不仅仅是JavaScript开发者,甚至是Rust,C#,Go语言的开发者,借助统一的编译机制,预先将这些语言开发的逻辑编译为浏览器可以执行的二进制代码格式,以此提高Web内容的性能和表现能力,同时为更多语言的开发者提供一种为Web开发内容的有效途径。” 注意,WebAssembly是可以在浏览器上直接执行的唯二语言。2017年,Chrome,Firefox,IE,Safari四个浏览器统一通过了WebAssembly的方案。如果说nodejs的出现让JavaScript的开发者能够染指后台开发的话,那么WebAssembly似乎要革了JavaScript的命,动摇了其在浏览器上的霸主地位,很多语言都能直接开发Web应用了。
2. JS工具库和基础库
Underscore和Lodash是非常优秀的JavaScript工具库,提供了非常多的工具方法用于操作集合、数组、方法、对象等。但是,当我们现在进行开发时,很多时候我们利用的方法已经被ES5与ES6所支持了,如果希望减少依赖的话,可以根据目标浏览器来选择不用Lodash或者Underscore。jQuery曾被誉为Web开发的瑞士军刀,是前端程序员的必备技能,解决了浏览器的兼容性问题,但是对DOM的随意操作会导致程序难以维护。随着各种现代框架的出现,jQuery的风头似乎正被掩盖,在google趋势上从2012年达到最高点后一路走低。不可否认的是,目前以及未来一段时间内,大多数Web应用仍然会依赖jQuery及其相关插件。
其它,跟Underscore和Lodash类似的还有Ramda,跟jQuery类似的有Prototype和Zepto。
为防止数据被无意更改而导致很隐晦的bug,可以考虑用Immutable.js,它可以将数据封装确保状态不被改变。
3. 前端MVC框架
通常前端技术选型最核心的决策点就是所谓的前端MVC框架,网上充斥着这类框架的对比文章,但是孰优孰劣仍然是萝卜白菜,各有所爱,说一句正确的废话就是:框架的选择要因地制宜,根据应用场景和团队实际情况来定。目前热度最高的三大前端框架是Angular(2.0+)、React和Vue.js。三者都采用了组件化的思想,同一component的html、js、css组织在一起,组件化思想对早期绝对的“关注点分离”则文件分离思想是一定程度的矫正,利于组件的单独开发和复用,减轻了html、js,特别是css的全局污染问题。
Angular是由Google推出的基于TypeScript的MVVM框架,视图和模型双向绑定,通过指令增强模板的表达能力,同时可以自定义组件化的指令,支持依赖注入、注解。由于策略上的问题,Angular2.0不兼容AngularJS 1.0,因此,近两年Angular的活跃度有所下降,然而其内置完备的特性和工具让拥护者们认为它是一个企业级的JS框架。
React由Facebook主推,最显著的特点是一切以JS为中心,HTML和CSS都由JS代码生成,为此,还产生了一种新的语法:JSX,这跟Angular把JS以扩展标签的形式放到HTML中是完全相反的做法。React实现了Virtual DOM,在DOM频繁变化的场景下,性能有不错的表现。另外,React本身主要关注于视图层,并不是一个大而全的框架,由于React认为MVC架构在大规模应用场景下会导致状态的混乱,因此创新地提出了单向数据流(Unidirectional data flow)的概念,出现了状态管理的模式或框架,如Flux/Redux。
Vue.js是三者之中最年轻、最轻量的一个框架,由华裔程序员尤雨溪发起,也是主要关注视图部分,既支持双向绑定也可以单向绑定。Vue.js的模板语法有点类似于Angular,提供了一些内置标签。Vue.js支持单文件组件,也就是将html、js、css写到一个后缀为.vue的文件中,也支持Virtual DOM,性能表现在三者中最好。Vue.js可以使用Redux做状态管理,但也提供了自己的Vuex。
值得一提的是,上面三个主流框架都有对应的移动端方案,比如Angular对应的Ionic,React的React Native以及Vue对应的Weex(阿里开发)。
数据和视图的不同交互方式催生出几个不同的概念:MVC、MVP、MVVM以及单向数据流。
下图是2017年不同国家对几大框架使用情况的调查反馈。
4. UI框架
上一节提到过React和Vue.js都专注于MVC的View,那它们跟UI框架什么区别呢?上一节说的View只关心DOM对象的生成及交互响应,而这一节里面的框架、库则真正决定UI长什么样、是否美观。这里继续细分的话又可以分为CSS框架(包括预处理器)、UI控件库、数据可视化库等等。所谓CSS框架其实也就是提前写好的一些CSS,只要在你的HTML中加上对应的类,就能展现出CSS应用的效果。CSS框架虽然也很多,但是影响力比较大的仍旧是老牌的Bootstrap、Foundation、Semantic-ui等,可能由于一些公司有内部UI规范或者CSS还没复杂到需要框架的原因,在stateofjs调查中,很多人不用CSS框架。从CSS发展的趋势看,移动优先,响应式布局,支持网格布局和Flexbox技术,这些是最新CSS框架着力发展的方向。
CSS预处理工具的主要目的是弥补CSS语法不够强大,解决书写啰嗦,维护困难等问题。目前最主流的三个预处理器是SASS/SCSS、LESS和Stylus,主要特性是提供了变量、函数和mixin、import以及一些逻辑控制语法等。不同于SASS等工具, PostCSS通过强大的插件体系,可以对CSS进行各种不同的转换和处理,包括语法检查 stylelint 插件、交叉编译sugarss 插件)、命名改编以避免选择器冲突( modules 插件 )、模板 CSS 代码生成( autoprefixer 插件 )、文件压缩等等。
CSS-in-JS是一种用JavaScript编写CSS样式的技术,通过鼓励采用一种通用模式,编写样式以及应用样式的JavaScript组件,使样式和逻辑的关注点得到统一。该领域中的新秀,诸如JSS、emotion和styled-components,依靠工具来将CSS-in-JS代码转化成独立的CSS样式表,从而适合在浏览器里运行。
UI控件库提供诸如Grid、Calendar、Panel、Scheduler等Web控件,常见的如Sencha Ext JS、jQuery UI、KendoUI、ant.d(蚂蚁金服出品)等。除了基础控件库,还有动画库如Popmotion,图标库如Font Awesome等。
数据可视化库提供各种统计图表,如bar chart、timeseries chart、pie chart等,比较有名的是Highcharts和百度开源的Echarts,前者基于SVG,后者基于Canvas。如果这两个库还不够用,可以考虑用D3自造*。
除此之外,AR/VR/MR也是近些年比较热的话题,相关技术在游戏娱乐、医疗影像等场景得到越来越多的应用。2017年10月W3C 的WebVR组发布了 WebVR 规范1.1版的初稿,2.0版本正在修订中。与此相关的JS框架、库包括:A-Frame,AR.js,Three.js,Babylon.js等。
5. 模块化和打包
一开始,Web应用还没有这么复杂,JavaScript根本没有模块化的概念,算得上封装的只是function和object,立即执行函数(IIFE)达到了封装私有变量的目的,避免了全局变量受到文件内变量的污染。随着前端应用的发展,命名冲突、依赖复杂导致的问题迫使人们开始考虑模块化的问题。模块系统将互相依赖的多个文件和目录拆分,所有代码都可以按需加载并彼此访问。最早,伴随着node.js的兴起,CommonJS成功解决了服务器端JS的模块化。但是由于其采用同步加载的方式,加载资源会造成浏览器的“假死”,所以并不适合Web应用的模块化。后来,AMD(异步模块定义)成为浏览器端模块加载的规范,比较流行的库是RequireJS。RequireJS要求模块定义使用define关键字,如下形式:define(['dep'], function(dep){}),模块导入使用require关键字。可见,RequireJS会预先加载依赖,完成后触发回调函数。与RequireJS类似的还有阿里大牛玉伯开发的SeaJS,但模块定义规范不同于AMD,它延续CommonJS规范,称为CMD(公共模块定义)。可惜的是,玉伯承认“现在可以给SeaJS立碑了”。
ES6之后,JavaScript语言本身终于支持模块化了。不需要将所有代码都放在一个IIFE或回调中,只需要在模块中声明需要的内容,所有的声明都被限定在模块的作用域中,对所有脚本和模块全局不可见,然后将需要暴露的模块资源使用export关键字导出,当其它模块依赖此模块时再通过import关键字导入。由于出现较晚,ES6的模块化还不太完善(比如按需加载),浏览器支持也有限,但可以预见,在不远的将来,ES内置的模块化将取代第三方库。
RequireJS/SeaJS可以认为是一种在线“编译”模块的方案,相当于在页面上加载一个AMD/CMD规范解释器。这样浏览器就能解析define,require,exports,module这些关键字,也就实现了模块化。而Browserify/Webpack是预编译模块打包的方案,不需要在浏览器中加载解释器。你在本地直接写JS,不管是AMD/CMD/ES6风格的模块化,它都能编译成浏览器认识的JS。
Webpack已经成为最流行的打包工具,它为所有的静态资源提供单一的依赖树,对JavaScript、CSS等资源进行灵活的操作,并将向浏览器发送内容的数量和次数最小化。其强大之处不仅仅在于它统一了JS的各种模块系统,取代了Browserify、RequireJS、SeaJS的工作,更重要的是它的万能模块加载理念,即所有的资源都可以且也应该模块化。
Parcel是刚刚出世不久的打包或者说是构建工具,是Webpack强有力的对手。相对于配置复杂的Webpack,它号称超级快(速度是Webpack的两倍)、零配置,同时还提供了一个开箱即用的开发服务器,通过热更新来支持快速开发。Parcel也有些不完善的地方,如不支持SourceMap,不能剔除无效代码(TreeShaking)。
6. 自动化构建
写完代码,在发布前,还需要进行构建,比如使用了TypeScript等方言,需要用Babel transpile到标准的ES5,如果用到CSS预处理工具如SASS,需要从.scss文件生成.css文件,进一步,为了提高前端加载效率,可能需要将js、css等文件打包压缩,生成雪碧图,等等。根据用到的技术和组件不同,构建流程包括的项目也不相同。grunt和gulp都是前端的自动化构建工具,grunt通过配置驱动,gulp是代码流式的处理方式,两者都有大量的插件支持各种任务。比较而言,gulp更加简单易用,上手更快,要优于grunt。
npm script就是在package.json里面的scripts属性直接定义需要执行的任务,因为是写在json文件里面,所以适合一些短小精悍的命令。
然而,由于webpack等打包工具代替了自动化构建工具的部分功能,显得自动化构建工具现在的作用不如以前了。
7. 包管理
曾经,NPM是Node模块的管理器,而Bower是前端模块管理器(另外比较有名的还有spm和component),前后端包管理分工明确。但是经过几年的发展,Bower四面楚歌,主要问题就是直接从git上拉取源码,没有统一的构建工具,缺少像NPM一样的registry。现在,官方已经不推荐使用Bower了:而NPM逐渐有一统JavaScript包管理江湖的趋势,大多数前端模块都能在NPM上找到。
然而在2017年,NPM的地位受到了Yarn的威胁!YARN 是一个新的包管理工具,它可替换现有NPM客户端的机制,同时兼容NPM注册表。如果使用NPM客户端,根据依赖库的不同安装顺序,它会在node_modules下得到一个不同的树结构,这种非确定性的特点可能导致“在我的机器上能工作”的问题。通过将安装步骤分解为解析、获取和链接,Yarn 使用确定性算法和 lockfiles避免了这些问题,从而确保重复安装的一致性。因为它对已经下载的包进行缓存,在持续集成(CI)环境中的构建速度明显更快。
8. 测试工具
根据功能和作用进行分类:* 提供测试环境(Mocha,Jasmine,Jest,Karma)
* 提供测试结构(Mocha,Jasmine,Jest,Cucumber)
* 提供断言功能(Chai,Jasmine,Jest,Unexpected)
* 生成,display和watch测试结果(Mocha,Jasmine,Jest,Karma)
* 生成和比较组件和数据结构的快照,以确保以前运行的更改(Jest,Ava)
* 提供mocks,spies和stubs(Sinon,Jasmine,enzyme,Jest,testdouble)
* 生成代码覆盖率报告(Istanbul,Jest)
* 提供一个浏览器或类似浏览器的环境,控制他们的场景执行(Protractor,Nightwatch,Phantom,Casper)
另外,Storybook 是一个用于定义、开发、测试UI组件的环境。让你可以创建和测试你的UI组件,就像一个在线的UI样式指南,对开发者非常有用。
9. 前后端协议
HTTP是Web应用最主要的前后端通信协议,目前应用广泛的Restful API也是基于HTTP协议的。当前主流的协议版本是1.1,诞生于1999年,当时Web应用数据交互还没有现在这么大,这么频繁,但是到了Web2.0时代,HTTP1.1的一些问题就暴露出来,比如连接无法复用和线头阻塞(Head-of-line blocking),这些问题导致HTTP请求时延过大,用户体验差。为解决这些问题,开发者首先是在应用层面对数据进行优化,比如通过构建对静态资源进行打包压缩,小图片生成雪碧图等方式,将多个文件合为一个,减少请求数以优化整个页面的加载速度。另外为规避HTTP同域请求数的限制,将静态资源部署在不同的域名上。在协议层面,人们通过建立HTTP长连接、http streaming、web socket等方式减少重建TCP连接带来的开销。
所有这些措施都只能是一些优化,没有根本解决问题的根源。2012年,Google提出了SPDY方案,后来在此基础上,IETF提出了HTTP2.0的方案。HTTP2.0首先解决了连接共享,即多路复用的问题,同时包括头压缩、二进制格式等特性,这些使得在请求数量大的场景下,页面加载速度大大提升。HTTP2.0正在不知不觉中普及,但在一段时间内会与1.1版本共存。
SOAP协议在SOA架构中曾经被广泛使用,但相对于其厚重,在Web2.0时代, 轻量级的REST架构成为主流,并在性能、效率、使用方便性上优于SOAP。REST最大的功劳在于前后端分离与无状态请求,而REST资源化的请求方式只适合面向简单的请求,对于具有复杂资源间关联关系或者个性化的请求就有点无能为力。在一些微服务化了的应用中,为了避免前端向多个微服务同时请求数据然后再组合,不得不增加一个data-view的后端微服务专门用于数据的抽取、转化。
GraphQL是Facebook推的标准,与之前Netflix的Falcor一样,都致力于解决上述问题,即:如何有效处理不断变化的Web/Mobile端复杂的数据请求。GraphQL可以让用户在内容和反馈数据的粒度两个方面掌握更多的控制权,但将更多的职责推到了服务层,必须要在服务端搭建符合 GraphQL spec的接口,改写服务端暴露数据的方式。
一些公司已经采用GraphQL,如Yelp、Spotify、Github等。GraphQL相关的类库有Meteor团队推出的Apollo Data以及Facebook自己推出的Relay。
上面这些技术的分类划分并没有严格的标准,而且这些分类也无法覆盖Web前端应用的所有问题域。上面有些框架跨越多个问题域,解决了相关的一系列问题,而有些工具则从不同角度或用不同方式解决相似的问题。Web前端应用场景的增加、规模的扩大以及技术的发展将会催生更多更新的框架、库,甚至出现新的前端设计理念,如“关注点混合”,“微前端”,“Web3.0”等,颠覆现有技术。
总结
随着“大前端”概念的兴起,前端开发已经超出了传统Web应用范畴,包括移动应用、VR甚至微信小程序等都纳入了前端范畴。前端生态空前兴盛,前端语言、框架之多跟后端相比有过之而无不及,本文仅对传统的Web前端生态做了粗浅的概括,限于篇幅,并不包括移动端Web应用及Web构建桌面应用等相关技术,希望能够起到一点提纲挈领的作用。最后,用一张思维导图完成总结:参考资料:
知乎,medium,segmentfault,hackernoon,微信公众号,还有很多… …