细细整理了过去接触过的那些前端技术,发现前端演进是段特别有意思的历史。人们老是在过去就作出将来须要的框架,而如今流行的是过去发明过的。如,响应式设计不得不提到的一个缺点是:它只是将本来在模板层作的事,放到了样式(CSS)层来完成。javascript
复杂度同力同样不会消失,也不会凭空产生,它老是从一个物体转移到另外一个物体或一种形式转为另外一种形式。css
若是6、七年前的移动网络速度和今天同样快,那么直接上的技术就是响应式设计,APP、SPA就不会流行得这么快。尽管咱们能够预见将来这些领域会变得更好,可是更须要的是改变现状。改变现状的同时也须要预见将来的需求。html
什么是前端?
维基百科是这样说的:前端(front-end)和后端(back-end)是描述进程开始和结束的通用词汇。前端做用于采集输入信息,后端进行处理。计算机程序的界面样式,视觉呈现属于前端。前端
这种说法给人一种很模糊的感受,可是他说得又很对,它负责视觉展现。在MVC结构或者MVP中,负责视觉显示的部分只有view层,而今天大多数所谓的View层已经超越了View层。前端是一个很神奇的概念,可是而今的前端已经发生了很大的变化。java
你引入了Backbone、Angluar,你的架构变成了MVP、MVVM。尽管发生了一些架构上的变化,可是项目的开发并无所以而发生变化。这其中涉及到了一些职责的问题,若是某一个层级中有太多的职责,那么它是否是加剧了一些人的负担?react
前端演进史
过去一直想整理一篇文章来讲说前端发展的历史,可是想着这些历史已经被人们所熟知。后来发现并不是如此,大抵是幸存者偏见——关注到的都知道这些历史。git
数据-模板-样式混合
在有限的前端经验里,我仍是经历了那段用Table来做样式的年代。大学期间曾经有偿帮一些公司或者我的开发、维护一些CMS,而Table是当时帮某个网站更新样式接触到的——ASP.Net(maybe)。当时,咱们启动这个CMS用的是一个名为aspweb.exe的程序。因而,在个人移动硬盘里找到了下面的代码。程序员
01 |
<TABLE cellSpacing=0 cellPadding=0 width=910 align=center border=0> |
04 |
<TD vAlign=top width=188><TABLE cellSpacing=0 cellPadding=0 width=184 align=center border=0> |
07 |
<TD>[站外图片上传中……(9)]</TD></TR> |
10 |
<TABLE cellSpacing=0 cellPadding=0 width=184 align=center |
11 |
background=Images/xxx.gif border=0> |
虽然,我也已经在HEAD里找到了现代的雏形——DIV + CSS,然而这仍然是一个Table的年代。github
1 |
<LINK href= "img/xxx.css" type=text/css rel=stylesheet> |
人们一直在说前端很难,问题是你学过么???web
人们一直在说前端很难,问题是你学过么???
人们一直在说前端很难,问题是你学过么???
也许,你也一直在说CSS很差写,可是CSS真的很差写么?人们总在说JS很难用,可是你学过么?只在须要的时候才去学,那确定很难。你未曾花时间去学习一门语言,可是却能直接写出能够work的代码,说明他们容易上手。若是你看过一些有经验的Ruby、Scala、Emacs Lisp开发者写出来的代码,我想会获得相同的结论。有一些语言可让写程序的人Happy,可是看的人可能就不Happy了。作事的方法不止一种,可是不是全部的人都要用那种方法去作。
过去的那些程序员都是真正的全栈程序员,这些程序员不只仅作了前端的活,还作了数据库的工做。
1 |
Set rs = Server.CreateObject( "ADODB.Recordset" ) |
2 |
sql = "select id,title,username,email,qq,adddate,content,Re_content,home,face,sex from Fl_Book where ispassed=1 order by id desc" |
3 |
rs.open sql, Conn, 1, 1 |
4 |
fl.SqlQueryNum = fl.SqlQueryNum + 1 |
在这个ASP文件里,它从数据库里查找出了数据,而后Render出HTML。若是能够看到历史版本,那么我想我会看到有一个做者将style=”"的代码一个个放到css文件中。
在这里的代码里也免不了有动态生成JavaScript代码的方法:
1 |
show_other = "<SCRIPT language=javascript>" |
2 |
show_other = show_other & "function checkform()" |
3 |
show_other = show_other & "{" |
4 |
show_other = show_other & "if (document.add.title.value=='')" |
5 |
show_other = show_other & "{" |
请尽情嘲笑,而后再看一段代码:
01 |
import React from "react" ; |
02 |
import { getData } from "../../common/request" ; |
03 |
import styles from "./style.css" ; |
05 |
export default class HomePage extends React.Component { |
06 |
componentWillMount() { |
07 |
console.log( "[HomePage] will mount with server response: " , this .props.data.home); |
11 |
let { title } = this .props.data.home; |
14 |
<div className={styles.content}> |
16 |
<p className={styles.welcomeText}>Thanks for joining!</p> |
21 |
static fetchData = function (params) { |
22 |
return getData( "/home" ); |
10年前和10年后的代码,彷佛没有太多的变化。有所不一样的是数据层已经被独立出去了,若是你的component也混合了数据层,即直接查询数据库而不是调用数据层接口,那么你就须要好好思考下这个问题。你只是在追随潮流,仍是在改变。用一个View层更换一个View层,用一个Router换一个Router的意义在哪?
Model-View-Controller
人们在不断地反思这其中复杂的过程,整理了一些好的架构模式,其中不得不提到的是我司Martin Folwer的《企业应用架构模式》。该书中文译版出版的时候是2004年,那时对于系统的分层是
层次 |
职责 |
表现层 |
提供服务、显示信息、用户请求、HTTP请求和命令行调用。 |
领域层 |
逻辑处理,系统中真正的核心。 |
数据层 |
与数据库、消息系统、事物管理器和其余软件包通信。 |
化身于当时最流行的Spring,就是MVC。人们有了iBatis这样的数据持久层框架,即ORM,对象关系映射。因而,你的package就会有这样的几个文件夹:
在mappers这一层,咱们所作的莫过于以下所示的数据库相关查询:
2 |
"INSERT INTO users(username, password, enabled) " + |
3 |
"VALUES (#{userName}, #{passwordHash}, #{enabled})" |
5 |
@Options(keyProperty = "id" , keyColumn = "id" , useGeneratedKeys = true ) |
6 |
void insert(User user); |
model文件夹和mappers文件夹都是数据层的一部分,只是二者间的职责不一样,如:
1 |
public String getUserName() { |
5 |
public void setUserName(String userName) { |
6 |
this .userName = userName; |
而他们最后都须要在Controller,又或者称为ModelAndView中处理:
01 |
@RequestMapping(value = { "/disableUser" }, method = RequestMethod.POST) |
02 |
public ModelAndView processUserDisable(HttpServletRequest request, ModelMap model) { |
03 |
String userName = request.getParameter( "userName" ); |
04 |
User user = userService.getByUsername(userName); |
05 |
userService.disable(user); |
06 |
Map<String,User> map = new HashMap<String,User>(); |
07 |
Map <User,String> usersWithRoles= userService.getAllUsersWithRole(); |
08 |
model.put( "usersWithRoles" ,usersWithRoles); |
09 |
return new ModelAndView( "redirect:users" ,map); |
在多数时候,Controller不该该直接与数据层的一部分,而将业务逻辑放在Controller层又是一种错误,这时就有了Service层,以下图:

Service MVC
然而对于Domain相关的Service应该放在哪一层,总会有不一样的意见:

MVC Player

MS MVC
Domain(业务)是一个至关复杂的层级,这里是业务的核心。一个合理的Controller只应该作本身应该作的事,它不该该处理业务相关的代码:
01 |
if (isNewnameEmpty == false && newuser == null ){ |
02 |
user.setUserName(newUsername); |
03 |
List<Post> myPosts = postService.findMainPostByAuthorNameSortedByCreateTime(principal.getName()); |
05 |
for (int k = 0;k < myPosts.size();k++){ |
06 |
Post post = myPosts.get(k); |
07 |
post.setAuthorName(newUsername); |
08 |
postService.save(post); |
10 |
userService.update(user); |
11 |
Authentication oldAuthentication = SecurityContextHolder.getContext().getAuthentication(); |
12 |
Authentication authentication = null ; |
13 |
if (oldAuthentication == null ){ |
14 |
authentication = new UsernamePasswordAuthenticationToken(newUsername,user.getPasswordHash()); |
16 |
authentication = new UsernamePasswordAuthenticationToken(newUsername,user.getPasswordHash(),oldAuthentication.getAuthorities()); |
18 |
SecurityContextHolder.getContext().setAuthentication(authentication); |
21 |
model.addAttribute( "myPosts" , myPosts); |
22 |
model.addAttribute( "namesuccess" , "User Profile updated successfully" ); |
23 |
return new ModelAndView( "user/profile" , map); |
咱们在Controller层应该作的事是:
-
处理请求的参数
-
渲染和重定向
-
选择Model和Service
-
处理Session和Cookies
业务是善变的,昨天咱们可能还在和对手竞争谁先推出新功能,可是今天可能已经合并了。咱们很难预见业务变化,可是咱们应该能预见Controller是不容易变化的。在一些设计里面,这种模式就是Command模式。
View层是一直在变化的层级,人们的品味一直在更新,有时甚至可能由于竞争对手而产生变化。在已经取得必定市场的状况下,Model- Service-Controller一般都不太会变更,甚至不敢变更。企业意识到创新的两面性,要么带来死亡,要么占领更大的市场。可是对手一般都比你想象中的更聪明一些,因此这时开创新的业务是一个更好的选择。
高速发展期的企业和发展初期的企业相比,更须要前端开发人员。在用户基数不够、业务待定的情形中,View只要可用并美观就好了,这时可能就会有大量的业务代码放在View层:
02 |
<c:when test= "${ hasError }" > |
03 |
<p class= "prompt-error" > |
04 |
${errors.username} ${errors.password} |
09 |
Woohoo, User <span class= "username" >${user.userName}</span> has been created successfully! |
不一样的情形下,人们都会对此有所争议,但只要符合当前的业务即是最好的选择。做为一个前端开发人员,在过去我须要修改JSP、PHP文件,这期间我须要去了解这些Template:
2 |
<li itemprop= "breadcrumb" ><span{ if (newest($v[ 'addtime' ],24))} style= "color:red" {/ if }>[{fun date( 'Y-m-d' ,$v[ 'addtime' ])}] |
3 |
</span><a href= "{$v['url']}" style= "{$v['style']}" target= "_blank" >{$v[ 'title' ]}</a></li> |
有时像Django这一类,自称为Model-Template-View的框架,更容易让人理解其意图:
01 |
{% for blog_post in blog_posts.object_list %} |
02 |
{% block blog_post_list_post_title %} |
03 |
<section class= "section--center mdl-grid mdl-grid--no-spacing mdl-shadow--2dp mdl-cell--11-col blog-list" > |
04 |
{% editable blog_post.title %} |
05 |
<div class= "mdl-card__title mdl-card--border mdl-card--expand" > |
06 |
<h2 class= "mdl-card__title-text" > |
07 |
<a href= "{{ blog_post.get_absolute_url }}" itemprop= "headline" >{{ blog_post.title }} › </a> |
做为一个前端人员,咱们真正在接触的是View层和Template层,可是MVC并无说明这些。
从桌面版到移动版
Wap出现了,并带来了更多的挑战。随后,分辨率从1024×768变成了176×208,开发人员不得不面临这些挑战。当时所须要作的仅仅是修改View层,而View层随着iPhone的出现又发生了变化。

WAP 网站
这是一个短暂的历史,PO还须要为手机用户制做一个怎样的网站?因而他们把桌面版的网站搬了过去变成了移动版。因为网络的缘由,每次都须要从新加载页面,这带来了不佳的用户体验。
幸运的是,人们很快意识到了这个问题,因而就有了SPA。若是当时的移动网络速度能够更快的话,我想不少SPA框架就不存在了。
先说说jQuery Mobile,在那以前,先让咱们来看看两个不一样版本的代码,下面是一个手机版本的blog详情页:
01 |
<ul data-role= "listview" data-inset= "true" data-splittheme= "a" > |
02 |
{% for blog_post in blog_posts.object_list %} |
04 |
{% editable blog_post.title blog_post.publish_date %} |
05 |
<h2 class= "blog-post-title" ><a href= "{% url " blog_post_detail " blog_post.slug %}" >{{ blog_post.title }}</a></h2> |
06 |
<em class= "since" >{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}</em> |
而下面是桌面版本的片断:
01 |
{% for blog_post in blog_posts.object_list %} |
02 |
{% block blog_post_list_post_title %} |
03 |
{% editable blog_post.title %} |
05 |
<a href= "{{ blog_post.get_absolute_url }}" >{{ blog_post.title }}</a> |
09 |
{% block blog_post_list_post_metainfo %} |
10 |
{% editable blog_post.publish_date %} |
11 |
<h6 class= "post-meta" > |
12 |
{% trans "Posted by" %}: |
13 |
{% with blog_post.user as author %} |
14 |
<a href= "{% url " blog_post_list_author " author %}" >{{ author.get_full_name| default :author.username }}</a> |
16 |
{% with blog_post.categories.all as categories %} |
19 |
{% for category in categories %} |
20 |
<a href= "{% url " blog_post_list_category " category.slug %}" >{{ category }}</a>{% if not forloop.last %}, {% endif %} |
24 |
{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %} |
人们所作的只是重载View层。这也是一个有效的SEO策略,上面这些代码是我博客过去的代码。对于桌面版和移动版都是不一样的模板和不一样的JS、CSS。

移动版网页
在这一时期,桌面版和移动版的代码可能在同一个代码库中。他们使用相同的代码,调用相同的逻辑,只是View层不一样了。可是,每次改动咱们都要维护两份代码。
随后,人们发现了一种更友好的移动版应用——APP。
APP与过渡期API
这是一个艰难的时刻,过去咱们的不少API都是在原来的代码库中构建的,即桌面版和移动版一块儿。咱们已经在这个代码库中开发了愈来愈多的功能,系统开发变得臃肿。如《Linux/Unix设计思想》中所说,这是一个伟大的系统,可是它臃肿而又缓慢。
咱们是选择从新开发一个结合第一和第二系统的最佳特性的第三个系统,仍是继续臃肿下去。我想你已经有答案了。随后咱们就有了APP API,构建出了博客的APP。

应用
最开始,人们愈来愈喜欢用APP,由于与移动版网页相比,其响应速度更快,并且更流畅。对于服务器来讲,也是一件好事,由于请求变少了。
可是并不是全部的人都会下载APP——有时只想看看上面有没有须要的东西。对于刚需不强的应用,人们并不会下载,只会访问网站。
有了APP API以后,咱们能够向网页提供API,咱们就开始设想要有一个好好的移动版。
过渡期SPA
Backbone诞生于2010年,和响应式设计出如今同一个年代里,但他们彷佛在同一个时代里火了起来。若是CSS3早点流行开来,彷佛就没有Backbone啥事了。不过移动网络仍是限制了响应式的流行,只是在今天这些都有所变化。
咱们用Ajax向后台请求API,而后Mustache Render出来。由于JavaScript在模块化上的缺陷,因此咱们就用Require.JS来进行模块化。
下面的代码就是我在尝试对个人博客进行SPA设计时的代码:
06 |
'json!/configure.json' , |
07 |
'text!/templates/blog_details.html' , |
09 |
], function ($, _, Mustache, ProductsView, configure, blogDetailsTemplate, GetBlog){ |
11 |
var BlogDetailsView = Backbone.View.extend ({ |
14 |
initialize: function () { |
15 |
this .params = '#content' ; |
18 |
getBlog: function (slug) { |
19 |
var getblog = new GetBlog( this .params, configure[ 'blogPostUrl' ] + slug, blogDetailsTemplate); |
24 |
return BlogDetailsView; |
从API获取数据,结合Template来Render出Page。可是这没法改变咱们须要Client Side Render和Server Side Render的两种Render方式,除非咱们能够像淘宝同样不须要考虑SEO——由于它不那么依靠搜索引擎带来流量。
这时,咱们仍是基于类MVC模式。只是数据的获取方式变成了Ajax,咱们就犯了一个错误——将大量的业务逻辑放在前端。这时候咱们已经不能再从View层直接访问Model层,从安全的角度来讲有点危险。
若是你的View层还能够直接访问Model层,那么说明你的架构仍是MVC模式。以前我在Github上构建一个Side Project的时候直接用View层访问了Model层,因为Model层是一个ElasticSearch的搜索引擎,它提供了JSON API,这使得我要在View层处理数据——即业务逻辑。将上述的JSON API放入Controller,尽管会加剧这一层的复杂度,可是业务逻辑就再也不放置于View层。
若是你在你的View层和Model层总有一层接口,那么你采用的就是MVP模式——MVC模式的衍生(PS:为了区别别的事情,总会有人取个表意的名称)。
一晚上以前,咱们又回到了过去。咱们离开了JSP,将View层变成了Template与Controller。而原有的Services层并非只承担其原来的责任,这些Services开始向ViewModel改变。
一些团队便将Services抽成多个Services,美其名为微服务。传统架构下的API从下图

API Gateway
变成了直接调用的微服务:

Micro Services
对于后台开发者来讲,这是一件大快人心的大好事,可是对于应用端/前端来讲并不是如此。调用的服务变多了,在应用程序端进行功能测试变得更复杂,须要Mock的API变多了。
Hybird与ViewModel
这时候遇到问题的不只仅只在前端,而在App端,小的团队已经没法承受开发成本。人们更多的注意力放到了Hybird应用上。Hybird应用解决了一些小团队在开发初期遇到的问题,这部分应用便交给了前端开发者。
前端开发人员先熟悉了单纯的JS + CSS + HTML,又熟悉了Router + PageView + API的结构,如今他们又须要作手机APP。这时候只好用熟悉的jQuer Mobile + Cordova。
随后,人们先从Cordova + jQuery Mobile,变成了Cordova + Angular的 Ionic。在那以前,一些团队可能已经用Angular代换了Backbone。他们须要更好的交互,须要data binding。
接着,咱们能够直接将咱们的Angular代码从前端移到APP,好比下面这种博客APP的代码:
01 |
.controller( 'BlogCtrl' , function ($scope, Blog) { |
03 |
$scope.blogOffset = 0; |
05 |
$scope.doRefresh = function () { |
06 |
Blog.async( 'https://www.phodal.com/api/v1/app/?format=json' ).then( function (results) { |
07 |
$scope.blogs = results.objects; |
09 |
$scope.$broadcast( 'scroll.refreshComplete' ); |
13 |
Blog.async( 'https://www.phodal.com/api/v1/app/?format=json' ).then( function (results) { |
14 |
$scope.blogs = results.objects; |
17 |
$scope.loadMore = function () { |
18 |
$scope.blogOffset = $scope.blogOffset + 1; |
19 |
Blog.async( 'https://www.phodal.com/api/v1/app/?limit=10&offset=' + $scope.blogOffset * 20 + '&format=json' ).then( function (results) { |
20 |
Array.prototype.push.apply($scope.blogs, results.objects); |
21 |
$scope.$broadcast( 'scroll.infiniteScrollComplete' ); |
结果时间轴又错了,人们老是超前一个时期作错了一个在将来是正确的决定。人们遇到了网页版的用户受权问题,因而发明了JWT——Json Web Token。
然而,因为WebView在一些早期的Android手机上出现了性能问题,人们开始考虑替换方案。接着出现了两个不一样的解决方案:
-
React Native
-
新的WebView——Crosswalk
开发人员开始欢呼React Native这样的框架。可是,他们并无预见到人们正在厌恶APP,APP在咱们的迭代里更新着,多是一星期,多是两星期,又或者是一个月。谁说APP内自更新不是一件坏事,可是APP的提醒无时无刻不在干扰着人们的生活,噪声愈来愈多。不要和用户争夺他们手机的使用权
一次构建,跨平台运行
在咱们须要学习C语言的时候,GCC就有了这样的跨平台编译。
在咱们开发桌面应用的时候,QT有就这样的跨平台能力。
在咱们构建Web应用的时候,Java有这样的跨平台能力。
在咱们须要开发跨平台应用的时候,Cordova有这样的跨平台能力。
如今,React这样的跨平台框架又出现了,而响应式设计也是跨平台式的设计。
响应式设计不得不提到的一个缺点是:他只是将本来在模板层作的事,放到了样式(CSS)层。你仍是在针对着不一样的设备进行设计,两种没有什么多大的不一样。复杂度不会消失,也不会凭空产生,它只会从一个物体转移到另外一个物体或一种形式转为另外一种形式。
React,将一小部分复杂度交由人来消化,将另一部分交给了React本身来消化。在用Spring MVC以前,也许咱们还在用CGI编程,而Spring下降了这部分复杂度,可是这和React同样下降的只是新手的复杂度。在咱们不能以某种语言的方式写某相关的代码时,这会带来诸多麻烦。
RePractise
若是你是一只辛勤的蜜蜂,那么我想你应该都玩过上面那些技术。你是在练习前端的技术,仍是在RePractise?若是你不花点时间整理一下过去,顺便预测一下将来,那么你就是在白搭。
前端的演进在这一年特别快,Ruby On Rails也在一个合适的年代里出现,在那个年代里也流行得特别快。RoR开发效率高的优点已然再也不突显,语法灵活性的反作用就是运行效率下降,同时后期维护难——每一个人元编程了本身。
若是不能把Controller、Model Mapper变成ViewModel,又或者是Micro Services来解耦,那么ES6 + React只是在如今带来更高的开发效率。而所谓的高效率,只是相比较而意淫出来的,由于他只是一层View层。将Model和Controller再加回View层,之后再拆分出来?
现有的结构只是将View层作了View层应该作的事。
首先,你应该考虑的是一种可让View层解耦于Domain或者Service层。今天,桌面、平板、手机并非惟一用户设备,虽然你可能在明年统一了这三个平台,如今新的设备的出现又将设备分红两种类型——桌面版和手机版。一开始桌面版和手机版是不一样的版本,后来你又须要合并这两个设备。
其次,你能够考虑用混合Micro Services优点的Monolithic Service来分解业务。若是能够举一个成功的例子,那么就是Linux,一个混合内核的“Service”。
最后,Keep Learning。咱们总须要在适当的时候作出改变,尽管咱们以为一个Web应用代码库中含桌面版和移动版代码会很不错,可是在那个时候须要作出改变。
对于复杂的应用来讲,其架构确定不是只有纯MVP或者纯MVVM这么简单的。若是一个应用混合了MVVM、MVP和MVC,那么他也变成了MVC——由于他直接访问了Model层。可是若是细分来看,只有访问了Model层的那一部分才是MVC模式。
模式,是人们对于某个解决方案的描述。在一段代码中可能有各类各样的设计模式,更况且是架构。
来自:https://github.com/phodal/repractise/blob/gh-pages/chapters/frontend.md
扩展阅读
咱们真的缺前端工程师吗?
2015 前端生态发展回顾
前端工程 - 基础篇
FEX 技术周刊 - 2016/01/25
从 Chukwa 到 Keystone :Netflix 的数据流水线演进
http://www.open-open.com/news/view/1a1d923