細細整理了過去接觸過的那些前端技術,發現前端演進是段特別有意思的歷史。人們老是在過去就作出將來須要的框架,而如今流行的是過去的過去發明過的。如,響應式設計不得不提到的一個缺點是:他只是將本來在模板層作的事,放到了樣式(CSS)層來完成。javascript
複雜度同力同樣不會消失,也不會憑空產生,它老是從一個物體轉移到另外一個物體或一種形式轉爲另外一種形式。php
若是6、七年前的移動網絡速度和今天同樣快,那麼直接上的技術就是響應式設計,APP、SPA就不會流行得這麼快。儘管咱們能夠預見將來這些領域會變得更好,可是更須要的是改變現狀。改變現狀的同時也須要預見將來的需求。css
維基百科是這樣說的:前端Front-end和後端back-end是描述進程開始和結束的通用詞彙。前端做用於採集輸入信息,後端進行處理。計算機程序的界面樣式,視覺呈現屬於前端。html
這種說法給人一種很模糊的感受,可是他說得又很對,它負責視覺展現。在MVC結構或者MVP中,負責視覺顯示的部分只有View層,而今天大多數所謂的View層已經超越了View層。前端是一個很神奇的概念,可是而今的前端已經發生了很大的變化。前端
你引入了Backbone、Angluar,你的架構變成了MVP、MVVM。儘管發生了一些架構上的變化,可是項目的開發並無所以而發生變化。這其中涉及到了一些職責的問題,若是某一個層級中有太多的職責,那麼它是否是加劇了一些人的負擔?java
過去一直想整理一篇文章來講說前端發展的歷史,可是想着這些歷史已經被人們所熟知。後來發現並不是如此,大抵是倖存者偏見——關注到的都知道這些歷史。react
在有限的前端經驗裏,我仍是經歷了那段用Table來做樣式的年代。大學期間曾經有償幫一些公司或者我的開發、維護一些CMS,而Table是當時幫某個網站更新樣式接觸到的——ASP.Net(maybe)。當時,咱們啓動這個CMS用的是一個名爲aspweb.exe
的程序。因而,在個人移動硬盤裏找到了下面的代碼。git
<TABLE cellSpacing=0 cellPadding=0 width=910 align=center border=0> <TBODY> <TR> <TD vAlign=top width=188><TABLE cellSpacing=0 cellPadding=0 width=184 align=center border=0> <TBODY> <TR> <TD><IMG src="Images/xxx.gif" width=184></TD></TR> <TR> <TD> <TABLE cellSpacing=0 cellPadding=0 width=184 align=center background=Images/xxx.gif border=0>
雖然,我也已經在HEAD裏找到了現代的雛形——DIV + CSS,然而這仍然是一個Table的年代。程序員
<LINK href="img/xxx.css" type=text/css rel=stylesheet>
人們一直在說前端很難,問題是你學過麼???github
人們一直在說前端很難,問題是你學過麼???
人們一直在說前端很難,問題是你學過麼???
也許,你也一直在說CSS很差寫,可是CSS真的很差寫麼?人們總在說JS很難用,可是你學過麼?只在須要的時候纔去學,那確定很難。你未曾花時間去學習一門語言,可是卻能直接寫出能夠work的代碼,說明他們容易上手。若是你看過一些有經驗的Ruby、Scala、Emacs Lisp開發者寫出來的代碼,我想會獲得相同的結論。有一些語言可讓寫程序的人Happy,可是看的人可能就不Happy了。作事的方法不止一種,可是不是全部的人都要用那種方法去作。
過去的那些程序員都是真正的全棧程序員,這些程序員不只僅作了前端的活,還作了數據庫的工做。
Set rs = Server.CreateObject("ADODB.Recordset") sql = "select id,title,username,email,qq,adddate,content,Re_content,home,face,sex from Fl_Book where ispassed=1 order by id desc" rs.open sql, Conn, 1, 1 fl.SqlQueryNum = fl.SqlQueryNum + 1
在這個ASP文件裏,它從數據庫裏查找出了數據,而後Render出HTML。若是能夠看到歷史版本,那麼我想我會看到有一個做者將style=""的代碼一個個放到css文件中。
在這裏的代碼裏也免不了有動態生成JavaScript代碼的方法:
show_other = "<SCRIPT language=javascript>" show_other = show_other & "function checkform()" show_other = show_other & "{" show_other = show_other & "if (document.add.title.value=='')" show_other = show_other & "{"
請盡情嘲笑,而後再看一段代碼:
import React from "react"; import { getData } from "../../common/request"; import styles from "./style.css"; export default class HomePage extends React.Component { componentWillMount() { console.log("[HomePage] will mount with server response: ", this.props.data.home); } render() { let { title } = this.props.data.home; return ( <div className={styles.content}> <h1>{title}</h1> <p className={styles.welcomeText}>Thanks for joining!</p> </div> ); } static fetchData = function(params) { return getData("/home"); } }
10年前和10年後的代碼,彷佛沒有太多的變化。有所不一樣的是數據層已經被獨立出去了,若是你的component也混合了數據層,即直接查詢數據庫而不是調用數據層接口,那麼你就須要好好思考下這個問題。你只是在追隨潮流,仍是在改變。用一個View層更換一個View層,用一個Router換一個Router的意義在哪?
人們在不斷地反思這其中複雜的過程,整理了一些好的架構模式,其中不得不提到的是我司Martin Folwer的《企業應用架構模式》。該書中文譯版出版的時候是2004年,那時對於系統的分層是
層次 | 職責 |
---|---|
表現層 | 提供服務、顯示信息、用戶請求、HTTP請求和命令行調用。 |
領域層 | 邏輯處理,系統中真正的核心。 |
數據層 | 與數據庫、消息系統、事物管理器和其餘軟件包通信。 |
化身於當時最流行的Spring,就是MVC。人們有了iBatis這樣的數據持久層框架,即ORM,對象關係映射。因而,你的package就會有這樣的幾個文件夾:
|____mappers |____model |____service |____utils |____controller
在mappers這一層,咱們所作的莫過於以下所示的數據庫相關查詢:
@Insert( "INSERT INTO users(username, password, enabled) " + "VALUES (#{userName}, #{passwordHash}, #{enabled})" ) @Options(keyProperty = "id", keyColumn = "id", useGeneratedKeys = true) void insert(User user);
model文件夾和mappers文件夾都是數據層的一部分,只是二者間的職責不一樣,如:
public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; }
而他們最後都須要在Controller,又或者稱爲ModelAndView中處理:
@RequestMapping(value = {"/disableUser"}, method = RequestMethod.POST) public ModelAndView processUserDisable(HttpServletRequest request, ModelMap model) { String userName = request.getParameter("userName"); User user = userService.getByUsername(userName); userService.disable(user); Map<String,User> map = new HashMap<String,User>(); Map <User,String> usersWithRoles= userService.getAllUsersWithRole(); model.put("usersWithRoles",usersWithRoles); return new ModelAndView("redirect:users",map); }
在多數時候,Controller不該該直接與數據層的一部分,而將業務邏輯放在Controller層又是一種錯誤,這時就有了Service層,以下圖:
然而對於Domain相關的Service應該放在哪一層,總會有不一樣的意見:
Domain(業務)是一個至關複雜的層級,這裏是業務的核心。一個合理的Controller只應該作本身應該作的事,它不該該處理業務相關的代碼:
if (isNewnameEmpty == false && newuser == null){ user.setUserName(newUsername); List<Post> myPosts = postService.findMainPostByAuthorNameSortedByCreateTime(principal.getName()); for (int k = 0;k < myPosts.size();k++){ Post post = myPosts.get(k); post.setAuthorName(newUsername); postService.save(post); } userService.update(user); Authentication oldAuthentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = null; if(oldAuthentication == null){ authentication = new UsernamePasswordAuthenticationToken(newUsername,user.getPasswordHash()); }else{ authentication = new UsernamePasswordAuthenticationToken(newUsername,user.getPasswordHash(),oldAuthentication.getAuthorities()); } SecurityContextHolder.getContext().setAuthentication(authentication); map.clear(); map.put("user",user); model.addAttribute("myPosts", myPosts); model.addAttribute("namesuccess", "User Profile updated successfully"); return new ModelAndView("user/profile", map); }
咱們在Controller層應該作的事是:
處理請求的參數
渲染和重定向
選擇Model和Service
處理Session和Cookies
業務是善變的,昨天咱們可能還在和對手競爭誰先推出新功能,可是今天可能已經合併了。咱們很難預見業務變化,可是咱們應該能預見Controller是不容易變化的。在一些設計裏面,這種模式就是Command模式。
View層是一直在變化的層級,人們的品味一直在更新,有時甚至可能由於競爭對手而產生變化。在已經取得必定市場的狀況下,Model-Service-Controller一般都不太會變更,甚至不敢變更。企業意識到創新的兩面性,要麼帶來死亡,要麼佔領更大的市場。可是對手一般都比你想象中的更聰明一些,因此這時開創新的業務是一個更好的選擇。
高速發展期的企業和發展初期的企業相比,更須要前端開發人員。在用戶基數不夠、業務待定的情形中,View只要可用並美觀就好了,這時可能就會有大量的業務代碼放在View層:
<c:choose> <c:when test="${ hasError }"> <p class="prompt-error"> ${errors.username} ${errors.password} </p> </c:when> <c:otherwise> <p class="prompt"> Woohoo, User <span class="username">${user.userName}</span> has been created successfully! </p> </c:otherwise> </c:choose>
不一樣的情形下,人們都會對此有所爭議,但只要符合當前的業務即是最好的選擇。做爲一個前端開發人員,在過去我須要修改JSP、PHP文件,這期間我須要去了解這些Template:
{foreach $lists as $v} <li itemprop="breadcrumb"><span{if(newest($v['addtime'],24))} style="color:red"{/if}>[{fun date('Y-m-d',$v['addtime'])}]</span><a href="{$v['url']}" style="{$v['style']}" target="_blank">{$v['title']}</a></li> {/foreach}
有時像Django這一類,自稱爲Model-Template-View的框架,更容易讓人理解其意圖:
{% for blog_post in blog_posts.object_list %} {% block blog_post_list_post_title %} <section class="section--center mdl-grid mdl-grid--no-spacing mdl-shadow--2dp mdl-cell--11-col blog-list"> {% editable blog_post.title %} <div class="mdl-card__title mdl-card--border mdl-card--expand"> <h2 class="mdl-card__title-text"> <a href="{{ blog_post.get_absolute_url }}" itemprop="headline">{{ blog_post.title }} › </a> </h2> </div> {% endeditable %} {% endblock %}
做爲一個前端人員,咱們真正在接觸的是View層和Template層,可是MVC並無說明這些。
Wap出現了,並帶來了更多的挑戰。隨後,分辨率從1024x768變成了176×208,開發人員不得不面臨這些挑戰。當時所須要作的僅僅是修改View層,而View層隨着iPhone的出現又發生了變化。
這是一個短暫的歷史,PO還須要爲手機用戶製做一個怎樣的網站?因而他們把桌面版的網站搬了過去變成了移動版。因爲網絡的緣由,每次都須要從新加載頁面,這帶來了不佳的用戶體驗。
幸運的是,人們很快意識到了這個問題,因而就有了SPA。若是當時的移動網絡速度能夠更快的話,我想不少SPA框架就不存在了。
先說說jQuery Mobile,在那以前,先讓咱們來看看兩個不一樣版本的代碼,下面是一個手機版本的blog詳情頁:
<ul data-role="listview" data-inset="true" data-splittheme="a"> {% for blog_post in blog_posts.object_list %} <li> {% editable blog_post.title blog_post.publish_date %} <h2 class="blog-post-title"><a href="{% url "blog_post_detail" blog_post.slug %}">{{ blog_post.title }}</a></h2> <em class="since">{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}</em> {% endeditable %} </li> {% endfor %} </ul>
而下面是桌面版本的片斷:
{% for blog_post in blog_posts.object_list %} {% block blog_post_list_post_title %} {% editable blog_post.title %} <h2> <a href="{{ blog_post.get_absolute_url }}">{{ blog_post.title }}</a> </h2> {% endeditable %} {% endblock %} {% block blog_post_list_post_metainfo %} {% editable blog_post.publish_date %} <h6 class="post-meta"> {% trans "Posted by" %}: {% with blog_post.user as author %} <a href="{% url "blog_post_list_author" author %}">{{ author.get_full_name|default:author.username }}</a> {% endwith %} {% with blog_post.categories.all as categories %} {% if categories %} {% trans "in" %} {% for category in categories %} <a href="{% url "blog_post_list_category" category.slug %}">{{ category }}</a>{% if not forloop.last %}, {% endif %} {% endfor %} {% endif %} {% endwith %} {% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %} </h6> {% endeditable %} {% endblock %}
人們所作的只是重載View層。這也是一個有效的SEO策略,上面這些代碼是我博客過去的代碼。對於桌面版和移動版都是不一樣的模板和不一樣的JS、CSS。
在這一時期,桌面版和移動版的代碼可能在同一個代碼庫中。他們使用相同的代碼,調用相同的邏輯,只是View層不一樣了。可是,每次改動咱們都要維護兩份代碼。
隨後,人們發現了一種更友好的移動版應用——APP。
這是一個艱難的時刻,過去咱們的不少API都是在原來的代碼庫中構建的,即桌面版和移動版一塊兒。咱們已經在這個代碼庫中開發了愈來愈多的功能,系統開發變得臃腫。如《Linux/Unix設計思想》中所說,這是一個偉大的系統,可是它臃腫而又緩慢。
咱們是選擇從新開發一個結合第一和第二系統的最佳特性的第三個系統,仍是繼續臃腫下去。我想你已經有答案了。隨後咱們就有了APP API,構建出了博客的APP。
最開始,人們愈來愈喜歡用APP,由於與移動版網頁相比,其響應速度更快,並且更流暢。對於服務器來講,也是一件好事,由於請求變少了。
可是並不是全部的人都會下載APP——有時只想看看上面有沒有須要的東西。對於剛需不強的應用,人們並不會下載,只會訪問網站。
有了APP API以後,咱們能夠向網頁提供API,咱們就開始設想要有一個好好的移動版。
Backbone誕生於2010年,和響應式設計出如今同一個年代裏,但他們彷佛在同一個時代裏火了起來。若是CSS3早點流行開來,彷佛就沒有Backbone啥事了。不過移動網絡仍是限制了響應式的流行,只是在今天這些都有所變化。
咱們用Ajax向後臺請求API,而後Mustache Render出來。由於JavaScript在模塊化上的缺陷,因此咱們就用Require.JS來進行模塊化。
下面的代碼就是我在嘗試對個人博客進行SPA設計時的代碼:
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; });
從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從下圖
變成了直接調用的微服務:
對於後臺開發者來講,這是一件大快人心的大好事,可是對於應用端/前端來講並不是如此。調用的服務變多了,在應用程序端進行功能測試變得更復雜,須要Mock的API變多了。
這時候遇到問題的不只僅只在前端,而在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的代碼:
.controller('BlogCtrl', function ($scope, Blog) { $scope.blogs = null; $scope.blogOffset = 0; // $scope.doRefresh = function () { Blog.async('https://www.phodal.com/api/v1/app/?format=json').then(function (results) { $scope.blogs = results.objects; }); $scope.$broadcast('scroll.refreshComplete'); $scope.$apply() }; Blog.async('https://www.phodal.com/api/v1/app/?format=json').then(function (results) { $scope.blogs = results.objects; }); $scope.loadMore = function() { $scope.blogOffset = $scope.blogOffset + 1; Blog.async('https://www.phodal.com/api/v1/app/?limit=10&offset='+ $scope.blogOffset * 20 + '&format=json').then(function (results) { Array.prototype.push.apply($scope.blogs, results.objects); $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?若是你不花點時間整理一下過去,順便預測一下將來,那麼你就是在白搭。
前端的演進在這一年特別快,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
關注微信公衆號: