前端演進(jìn)史#
引言:前不久開始寫技術(shù)分享文章的時(shí)候惠爽,一直想從寫一篇關(guān)于前端簡史相關(guān)的文章開始丧诺。所以届惋,一直在準(zhǔn)備著髓帽,所以第一次就先分享了一篇webpack初級(jí)學(xué)習(xí)篇。一次脑豹,我們前端組長在群里分享了這篇文章郑藏,我一看,頓時(shí)眼前一亮啊晨缴,這不就是哥們需要的嗎译秦,哈哈...。額,得意忘形了筑悴,嗯们拙,我們回到正軌好吧。這次先分享下這篇良心之作吧阁吝,本人水平有限砚婆,接觸前端時(shí)間過短,沒什么資格對(duì)大神之作進(jìn)行改動(dòng)突勇,就直接重新排了下装盯。這篇結(jié)束后,下面我會(huì)分享一篇前端三大內(nèi)功心法:HTML/CSS/JAVASCRIPT,在瀏覽器這個(gè)大江湖甲馋,大染缸中是怎樣游戲江湖埂奈,游龍戲水的。
細(xì)細(xì)整理了過去接觸過的那些前端技術(shù)定躏,發(fā)現(xiàn)前端演進(jìn)是段特別有意思的歷史账磺。人們總是在過去就做出未來需要的框架,而現(xiàn)在流行的是過去的過去發(fā)明過的痊远。如垮抗,響應(yīng)式設(shè)計(jì)不得不提到的一個(gè)缺點(diǎn)是:他只是將原本在模板層做的事,放到了樣式(CSS)層來完成碧聪。
復(fù)雜度同力一樣不會(huì)消失冒版,也不會(huì)憑空產(chǎn)生,它總是從一個(gè)物體轉(zhuǎn)移到另一個(gè)物體或一種形式轉(zhuǎn)為另一種形式逞姿。
如果六辞嗡、七年前的移動(dòng)網(wǎng)絡(luò)速度和今天一樣快,那么直接上的技術(shù)就是響應(yīng)式設(shè)計(jì)哼凯,APP欲间、SPA就不會(huì)流行得這么快楚里。盡管我們可以預(yù)見未來這些領(lǐng)域會(huì)變得更好断部,但是更需要的是改變現(xiàn)狀。改變現(xiàn)狀的同時(shí)也需要預(yù)見未來的需求班缎。
什么是前端蝴光?##
我也對(duì)現(xiàn)在的前端概念不是非常清晰,雖然我是做前端的达址,現(xiàn)在提出全棧蔑祟,前端目前接觸的感覺,好像都得懂點(diǎn)/(ㄒoㄒ)/~~沉唠。
維基百科是這樣說的:前端Front-end和后端back-end是描述進(jìn)程開始和結(jié)束的通用詞匯疆虚。前端作用于采集輸入信息,后端進(jìn)行處理。計(jì)算機(jī)程序的界面樣式径簿,視覺呈現(xiàn)屬于前端罢屈。
這種說法給人一種很模糊的感覺,但是他說得又很對(duì)篇亭,它負(fù)責(zé)視覺展示缠捌。在MVC結(jié)構(gòu)或者M(jìn)VP中,負(fù)責(zé)視覺顯示的部分只有View層译蒂,而今天大多數(shù)所謂的View層已經(jīng)超越了View層曼月。前端是一個(gè)很神奇的概念,但是而今的前端已經(jīng)發(fā)生了很大的變化柔昼。
你引入了Backbone哑芹、Angluar,你的架構(gòu)變成了MVP捕透、MVVM绩衷。盡管發(fā)生了一些架構(gòu)上的變化,但是項(xiàng)目的開發(fā)并沒有因此而發(fā)生變化激率。這其中涉及到了一些職責(zé)的問題咳燕,如果某一個(gè)層級(jí)中有太多的職責(zé),那么它是不是加重了一些人的負(fù)擔(dān)乒躺?
前端演進(jìn)史##
重點(diǎn)來了U忻ぁ!ヾ(o???)?ヾ
過去一直想整理一篇文章來說說前端發(fā)展的歷史嘉冒,但是想著這些歷史已經(jīng)被人們所熟知曹货。后來發(fā)現(xiàn)并非如此,大抵是幸存者偏見——關(guān)注到的都知道這些歷史讳推。
數(shù)據(jù)-模板-樣式混合###
這種模式目前已經(jīng)被廣大的前端工程師給拋棄了顶籽,不過還是有些后端工程師們寫的前端頁面是這樣的。我們接手就會(huì)O__O "…
在有限的前端經(jīng)驗(yàn)里银觅,我還是經(jīng)歷了那段用Table來作樣式的年代礼饱。大學(xué)期間曾經(jīng)有償幫一些公司或者個(gè)人開發(fā)、維護(hù)一些CMS究驴,而Table是當(dāng)時(shí)幫某個(gè)網(wǎng)站更新樣式接觸到的——ASP.Net(maybe)镊绪。當(dāng)時(shí),我們啟動(dòng)這個(gè)CMS用的是一個(gè)名為aspweb.exe的程序洒忧。于是蝴韭,在我的移動(dòng)硬盤里找到了下面的代碼。
<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>[站外圖片上傳中……(10)]</TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=184 align=center
background=Images/xxx.gif border=0>
雖然熙侍,我也已經(jīng)在HEAD里找到了現(xiàn)代的雛形——DIV + CSS榄鉴,然而這仍然是一個(gè)Table的年代履磨。
<LINK href="img/xxx.css" type=text/css rel=stylesheet>
人們一直在說前端很難,問題是你學(xué)過么庆尘?蹬耘??
人們一直在說前端很難减余,問題是你學(xué)過么综苔??位岔?
人們一直在說前端很難如筛,問題是你學(xué)過么?抒抬?杨刨?
也許,你也一直在說CSS不好寫擦剑,但是CSS真的不好寫么妖胀?人們總在說JS很難用,但是你學(xué)過么惠勒?只在需要的時(shí)候才去學(xué)赚抡,那肯定很難。你不曾花時(shí)間去學(xué)習(xí)一門語言纠屋,但是卻能直接寫出可以work的代碼涂臣,說明他們?nèi)菀咨鲜?/strong>。如果你看過一些有經(jīng)驗(yàn)的Ruby售担、Scala赁遗、Emacs Lisp開發(fā)者寫出來的代碼,我想會(huì)得到相同的結(jié)論族铆。有一些語言可以讓寫程序的人Happy岩四,但是看的人可能就不Happy了。做事的方法不止一種哥攘,但是不是所有的人都要用那種方法去做剖煌。
過去的那些程序員都是真正的全棧程序員,這些程序員不僅僅做了前端的活献丑,還做了數(shù)據(jù)庫的工作末捣。
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
在這個(gè)ASP文件里,它從數(shù)據(jù)庫里查找出了數(shù)據(jù)创橄,然后Render出HTML。如果可以看到歷史版本莽红,那么我想我會(huì)看到有一個(gè)作者將style=""的代碼一個(gè)個(gè)放到css文件中妥畏。
在這里的代碼里也免不了有動(dòng)態(tài)生成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年后的代碼,似乎沒有太多的變化醉蚁。有所不同的是數(shù)據(jù)層已經(jīng)被獨(dú)立出去了燃辖,如果你的component也混合了數(shù)據(jù)層,即直接查詢數(shù)據(jù)庫而不是調(diào)用數(shù)據(jù)層接口网棍,那么你就需要好好思考下這個(gè)問題黔龟。你只是在追隨潮流,還是在改變滥玷。用一個(gè)View層更換一個(gè)View層氏身,用一個(gè)Router換一個(gè)Router的意義在哪?
Model-View-Controller###
這也就最近幾年很火的MVC開發(fā)模式惑畴,雖然我用過angular,但是對(duì)這個(gè)了解的還是不是很深刻╮(╯▽╰)╭
人們在不斷地反思這其中復(fù)雜的過程蛋欣,整理了一些好的架構(gòu)模式,其中不得不提到的是我司Martin Folwer的《企業(yè)應(yīng)用架構(gòu)模式》如贷。該書中文譯版出版的時(shí)候是2004年陷虎,那時(shí)對(duì)于系統(tǒng)的分層是
層次 | 職責(zé) |
---|---|
表現(xiàn)層 | 提供服務(wù)、顯示信息杠袱、用戶請求尚猿、HTTP請求和命令行調(diào)用。 |
領(lǐng)域?qū)?/td> | 邏輯處理楣富,系統(tǒng)中真正的核心谊路。 |
數(shù)據(jù)層 | 與數(shù)據(jù)庫、消息系統(tǒng)菩彬、事物管理器和其他軟件包通訊缠劝。 |
化身于當(dāng)時(shí)最流行的Spring,就是MVC骗灶。人們有了iBatis這樣的數(shù)據(jù)持久層框架惨恭,即ORM,對(duì)象關(guān)系映射耙旦。于是脱羡,你的package就會(huì)有這樣的幾個(gè)文件夾:
|____mappers
|____model
|____service
|____utils
|____controller
在mappers這一層,我們所做的莫過于如下所示的數(shù)據(jù)庫相關(guān)查詢:
@Insert(
"INSERT INTO users(username, password, enabled) " +
"VALUES (#{userName}, #{passwordHash}, #{enabled})"
)
@Options(keyProperty = "id", keyColumn = "id", useGeneratedKeys = true)
void insert(User user);
model文件夾和mappers文件夾都是數(shù)據(jù)層的一部分免都,只是兩者間的職責(zé)不同锉罐,如:
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);
}
在多數(shù)時(shí)候绕娘,Controller不應(yīng)該直接與數(shù)據(jù)層的一部分脓规,而將業(yè)務(wù)邏輯放在Controller層又是一種錯(cuò)誤,這時(shí)就有了Service層险领,如下圖:
然而對(duì)于Domain相關(guān)的Service應(yīng)該放在哪一層侨舆,總會(huì)有不同的意見:
Domain(業(yè)務(wù))是一個(gè)相當(dāng)復(fù)雜的層級(jí)秒紧,這里是業(yè)務(wù)的核心。一個(gè)合理的Controller只應(yīng)該做自己應(yīng)該做的事挨下,它不應(yīng)該處理業(yè)務(wù)相關(guān)的代碼:
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層應(yīng)該做的事是:
處理請求的參數(shù)
渲染和重定向
選擇Model和Service
處理Session和Cookies
業(yè)務(wù)是善變的熔恢,昨天我們可能還在和對(duì)手競爭誰先推出新功能,但是今天可能已經(jīng)合并了臭笆。我們很難預(yù)見業(yè)務(wù)變化叙淌,但是我們應(yīng)該能預(yù)見Controller是不容易變化的。在一些設(shè)計(jì)里面愁铺,這種模式就是Command模式鹰霍。
View層是一直在變化的層級(jí),人們的品味一直在更新帜讲,有時(shí)甚至可能因?yàn)楦偁帉?duì)手而產(chǎn)生變化衅谷。在已經(jīng)取得一定市場的情況下,Model-Service-Controller通常都不太會(huì)變動(dòng)似将,甚至不敢變動(dòng)获黔。企業(yè)意識(shí)到創(chuàng)新的兩面性,要么帶來死亡在验,要么占領(lǐng)更大的市場玷氏。但是對(duì)手通常都比你想象中的更聰明一些,所以這時(shí)開創(chuàng)新的業(yè)務(wù)是一個(gè)更好的選擇腋舌。
高速發(fā)展期的企業(yè)和發(fā)展初期的企業(yè)相比盏触,更需要前端開發(fā)人員。在用戶基數(shù)不夠块饺、業(yè)務(wù)待定的情形中赞辩,View只要可用并美觀就行了,這時(shí)可能就會(huì)有大量的業(yè)務(wù)代碼放在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>
不同的情形下授艰,人們都會(huì)對(duì)此有所爭議辨嗽,但只要符合當(dāng)前的業(yè)務(wù)便是最好的選擇。作為一個(gè)前端開發(fā)人員淮腾,在過去我需要修改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}
有時(shí)像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 %}
作為一個(gè)前端人員,我們真正在接觸的是View層和Template層圆凰,但是MVC并沒有說明這些杈帐。
從桌面板到移動(dòng)版###
web前端進(jìn)軍移動(dòng)界,這是個(gè)值得慶祝的歷程 送朱,我還沒怎么接觸過移動(dòng)端娘荡,╮(╯▽╰)╭
Wap出現(xiàn)了干旁,并帶來了更多的挑戰(zhàn)驶沼。隨后炮沐,分辨率從1024x768變成了176×208,開發(fā)人員不得不面臨這些挑戰(zhàn)回怜。當(dāng)時(shí)所需要做的僅僅是修改View層大年,而View層隨著iPhone的出現(xiàn)又發(fā)生了變化。
這是一個(gè)短暫的歷史玉雾,PO還需要為手機(jī)用戶制作一個(gè)怎樣的網(wǎng)站翔试?于是他們把桌面版的網(wǎng)站搬了過去變成了移動(dòng)版。由于網(wǎng)絡(luò)的原因复旬,每次都需要重新加載頁面垦缅,這帶來了不佳的用戶體驗(yàn)。
幸運(yùn)的是驹碍,人們很快意識(shí)到了這個(gè)問題壁涎,于是就有了SPA。如果當(dāng)時(shí)的移動(dòng)網(wǎng)絡(luò)速度可以更快的話志秃,我想很多SPA框架就不存在了怔球。
先說說jQuery Mobile,在那之前浮还,先讓我們來看看兩個(gè)不同版本的代碼竟坛,下面是一個(gè)手機(jī)版本的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層。這也是一個(gè)有效的SEO策略钧舌,上面這些代碼是我博客過去的代碼担汤。對(duì)于桌面版和移動(dòng)版都是不同的模板和不同的JS、CSS洼冻。
在這一時(shí)期崭歧,桌面版和移動(dòng)版的代碼可能在同一個(gè)代碼庫中。他們使用相同的代碼碘赖,調(diào)用相同的邏輯驾荣,只是View層不同了。但是普泡,每次改動(dòng)我們都要維護(hù)兩份代碼播掷。
隨后,人們發(fā)現(xiàn)了一種更友好的移動(dòng)版應(yīng)用——APP撼班。
APP與過度期API###
APP伴隨著智能手機(jī)時(shí)代的到來歧匈,尤其是一些水果手機(jī),一大批人因此身體少了個(gè)部件砰嘁。(⊙o⊙)…
這是一個(gè)艱難的時(shí)刻件炉,過去我們的很多API都是在原來的代碼庫中構(gòu)建的勘究,即桌面版和移動(dòng)版一起。我們已經(jīng)在這個(gè)代碼庫中開發(fā)了越來越多的功能斟冕,系統(tǒng)開發(fā)變得臃腫口糕。如《Linux/Unix設(shè)計(jì)思想》中所說,這是一個(gè)偉大的系統(tǒng)磕蛇,但是它臃腫而又緩慢景描。
我們是選擇重新開發(fā)一個(gè)結(jié)合第一和第二系統(tǒng)的最佳特性的第三個(gè)系統(tǒng),還是繼續(xù)臃腫下去秀撇。我想你已經(jīng)有答案了超棺。隨后我們就有了APP API,構(gòu)建出了博客的APP呵燕。
最開始棠绘,人們越來越喜歡用APP,因?yàn)榕c移動(dòng)版網(wǎng)頁相比再扭,其響應(yīng)速度更快氧苍,而且更流暢。對(duì)于服務(wù)器來說霍衫,也是一件好事候引,因?yàn)檎埱笞兩倭恕?/p>
但是并非所有的人都會(huì)下載APP——有時(shí)只想看看上面有沒有需要的東西。對(duì)于剛需不強(qiáng)的應(yīng)用敦跌,人們并不會(huì)下載澄干,只會(huì)訪問網(wǎng)站前鹅。
有了APP API之后裸弦,我們可以向網(wǎng)頁提供API,我們就開始設(shè)想要有一個(gè)好好的移動(dòng)版吵瞻。
過渡期SPA###
SPA開發(fā)模式惧笛,也就是單頁面開發(fā)模式从媚,通過路由進(jìn)行頁面中的某塊區(qū)域更換刷新,當(dāng)下也是比較流行的一種開發(fā)方式患整,但是也是會(huì)導(dǎo)致一些別的問題復(fù)雜化拜效。(⊙o⊙)…
Backbone誕生于2010年,和響應(yīng)式設(shè)計(jì)出現(xiàn)在同一個(gè)年代里各谚,但他們似乎在同一個(gè)時(shí)代里火了起來紧憾。如果CSS3早點(diǎn)流行開來,似乎就沒有Backbone啥事了昌渤。不過移動(dòng)網(wǎng)絡(luò)還是限制了響應(yīng)式的流行赴穗,只是在今天這些都有所變化。
我們用Ajax向后臺(tái)請求API,然后Mustache Render出來般眉。因?yàn)镴avaScript在模塊化上的缺陷了赵,所以我們就用Require.JS來進(jìn)行模塊化。
下面的代碼就是我在嘗試對(duì)我的博客進(jìn)行SPA設(shè)計(jì)時(shí)的代碼:
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獲取數(shù)據(jù)甸赃,結(jié)合Template來Render出Page柿汛。但是這無法改變我們需要Client Side Render和Server Side Render的兩種Render方式,除非我們可以像淘寶一樣不需要考慮SEO——因?yàn)樗荒敲匆揽克阉饕鎺砹髁俊?/p>
這時(shí)辑奈,我們還是基于類MVC模式苛茂。只是數(shù)據(jù)的獲取方式變成了Ajax已烤,我們就犯了一個(gè)錯(cuò)誤——將大量的業(yè)務(wù)邏輯放在前端鸠窗。這時(shí)候我們已經(jīng)不能再從View層直接訪問Model層,從安全的角度來說有點(diǎn)危險(xiǎn)胯究。
如果你的View層還可以直接訪問Model層稍计,那么說明你的架構(gòu)還是MVC模式。之前我在Github上構(gòu)建一個(gè)Side Project的時(shí)候直接用View層訪問了Model層裕循,由于Model層是一個(gè)ElasticSearch的搜索引擎臣嚣,它提供了JSON API,這使得我要在View層處理數(shù)據(jù)——即業(yè)務(wù)邏輯剥哑。將上述的JSON API放入Controller硅则,盡管會(huì)加重這一層的復(fù)雜度,但是業(yè)務(wù)邏輯就不再放置于View層株婴。
如果你在你的View層和Model層總有一層接口怎虫,那么你采用的就是MVP模式——MVC模式的衍生(PS:為了區(qū)別別的事情,總會(huì)有人取個(gè)表意的名稱)困介。
一夜之前大审,我們又回到了過去。我們離開了JSP座哩,將View層變成了Template與Controller徒扶。而原有的Services層并不是只承擔(dān)其原來的責(zé)任,這些Services開始向ViewModel改變根穷。
一些團(tuán)隊(duì)便將Services抽成多個(gè)Services姜骡,美其名為微服務(wù)。傳統(tǒng)架構(gòu)下的API從下圖
變成這樣:
對(duì)于后臺(tái)開發(fā)者來說屿良,這是一件大快人心的大好事圈澈,但是對(duì)于應(yīng)用端/前端來說并非如此。調(diào)用的服務(wù)變多了管引,在應(yīng)用程序端進(jìn)行功能測試變得更復(fù)雜士败,需要Mock的API變多了。
Hybird與ViewModel###
原生應(yīng)用和視圖數(shù)據(jù)層,衍生出了一些混合應(yīng)用谅将。就和現(xiàn)在的混血兒一樣漾狼,但是有時(shí)混血不一定成功。/(ㄒoㄒ)/~~
這時(shí)候遇到問題的不僅僅只在前端饥臂,而在App端逊躁,小的團(tuán)隊(duì)已經(jīng)無法承受開發(fā)成本。人們更多的注意力放到了Hybird應(yīng)用上隅熙。Hybird應(yīng)用解決了一些小團(tuán)隊(duì)在開發(fā)初期遇到的問題稽煤,這部分應(yīng)用便交給了前端開發(fā)者。
前端開發(fā)人員先熟悉了單純的JS + CSS + HTML囚戚,又熟悉了Router + PageView + API的結(jié)構(gòu)酵熙,現(xiàn)在他們又需要做手機(jī)APP。這時(shí)候只好用熟悉的jQuer Mobile + Cordova驰坊。
隨后匾二,人們先從Cordova + jQuery Mobile,變成了Cordova + Angluar的 Ionic拳芙。在那之前察藐,一些團(tuán)隊(duì)可能已經(jīng)用Angluar代換了Backbone。他們需要更好的交互舟扎,需要data binding分飞。
接著,我們可以直接將我們的Angluar代碼從前端移到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');
})
};
})
結(jié)果時(shí)間軸又錯(cuò)了譬猫,人們總是超前一個(gè)時(shí)期做錯(cuò)了一個(gè)在未來是正確的決定。人們遇到了網(wǎng)頁版的用戶授權(quán)問題邦泄,于是發(fā)明了JWK——Json Web Token删窒。
然而,由于WebView在一些早期的Android手機(jī)上出現(xiàn)了性能問題顺囊,人們開始考慮替換方案肌索。接著出現(xiàn)了兩個(gè)不同的解決方案:
- React Native
- 新的WebView——Crosswalk
開發(fā)人員開始?xì)g呼React Native這樣的框架。但是特碳,他們并沒有預(yù)見到人們正在厭惡APP诚亚,APP在我們的迭代里更新著,可能是一星期午乓,可能是兩星期站宗,又或者是一個(gè)月。誰說APP內(nèi)自更新不是一件壞事益愈,但是APP的提醒無時(shí)無刻不在干擾著人們的生活梢灭,噪聲越來越多夷家。不要和用戶爭奪他們手機(jī)的使用權(quán)
一次構(gòu)建,跨平臺(tái)運(yùn)行###
在當(dāng)下這種互聯(lián)網(wǎng)敏释,物聯(lián)網(wǎng)時(shí)代库快,應(yīng)了江湖流傳下的一句話:天下武功,唯快不破钥顽∫迤粒跨平臺(tái),正是現(xiàn)在這種高速運(yùn)行下的社會(huì)發(fā)展所需的產(chǎn)物蜂大。(⊙o⊙)…
在我們需要學(xué)習(xí)C語言的時(shí)候闽铐,GCC就有了這樣的跨平臺(tái)編譯。
在我們開發(fā)桌面應(yīng)用的時(shí)候奶浦,QT有就這樣的跨平臺(tái)能力兄墅。
在我們構(gòu)建Web應(yīng)用的時(shí)候,Java有這樣的跨平臺(tái)能力财喳。
在我們需要開發(fā)跨平臺(tái)應(yīng)用的時(shí)候察迟,Cordova有這樣的跨平臺(tái)能力。
現(xiàn)在耳高,React這樣的跨平臺(tái)框架又出現(xiàn)了,而響應(yīng)式設(shè)計(jì)也是跨平臺(tái)式的設(shè)計(jì)所踊。
響應(yīng)式設(shè)計(jì)不得不提到的一個(gè)缺點(diǎn)是:他只是將原本在模板層做的事泌枪,放到了樣式(CSS)層。你還是在針對(duì)著不同的設(shè)備進(jìn)行設(shè)計(jì)秕岛,兩種沒有什么多大的不同碌燕。復(fù)雜度不會(huì)消失,也不會(huì)憑空產(chǎn)生继薛,它只會(huì)從一個(gè)物體轉(zhuǎn)移到另一個(gè)物體或一種形式轉(zhuǎn)為另一種形式修壕。
React,將一小部分復(fù)雜度交由人來消化遏考,將另外一部分交給了React自己來消化慈鸠。在用Spring MVC之前,也許我們還在用CGI編程灌具,而Spring降低了這部分復(fù)雜度青团,但是這和React一樣降低的只是新手的復(fù)雜度。在我們不能以某種語言的方式寫某相關(guān)的代碼時(shí)咖楣,這會(huì)帶來諸多麻煩督笆。
結(jié)語##
如果你是一只辛勤的蜜蜂,那么我想你應(yīng)該都玩過上面那些技術(shù)诱贿。你是在練習(xí)前端的技術(shù)娃肿,還是在RePractise?如果你不花點(diǎn)時(shí)間整理一下過去,順便預(yù)測一下未來料扰,那么你就是在白搭锨阿。
前端的演進(jìn)在這一年特別快,Ruby On Rails也在一個(gè)合適的年代里出現(xiàn)记罚,在那個(gè)年代里也流行得特別快墅诡。RoR開發(fā)效率高的優(yōu)勢已然不再突顯,語法靈活性的副作用就是運(yùn)行效率降低桐智,同時(shí)后期維護(hù)難——每個(gè)人元編程了自己末早。
如果不能把Controller、Model Mapper變成ViewModel说庭,又或者是Micro Services來解耦然磷,那么ES6 + React只是在現(xiàn)在帶來更高的開發(fā)效率。而所謂的高效率刊驴,只是相比較而意淫出來的姿搜,因?yàn)樗皇且粚覸iew層。將Model和Controller再加回View層捆憎,以后再拆分出來舅柜?
現(xiàn)有的結(jié)構(gòu)只是將View層做了View層應(yīng)該做的事。
首先躲惰,你應(yīng)該考慮的是一種可以讓View層解耦于Domain或者Service層致份。今天,桌面础拨、平板氮块、手機(jī)并不是唯一用戶設(shè)備,雖然你可能在明年統(tǒng)一了這三個(gè)平臺(tái)诡宗,現(xiàn)在新的設(shè)備的出現(xiàn)又將設(shè)備分成兩種類型——桌面版和手機(jī)版滔蝉。一開始桌面版和手機(jī)版是不同的版本,后來你又需要合并這兩個(gè)設(shè)備塔沃。
其次蝠引,你可以考慮用混合Micro Services優(yōu)勢的Monolithic Service來分解業(yè)務(wù)。如果可以舉一個(gè)成功的例子芳悲,那么就是Linux立肘,一個(gè)混合內(nèi)核的“Service”。
最后名扛,Keep Learning谅年。我們總需要在適當(dāng)?shù)臅r(shí)候做出改變,盡管我們覺得一個(gè)Web應(yīng)用代碼庫中含桌面版和移動(dòng)版代碼會(huì)很不錯(cuò)肮韧,但是在那個(gè)時(shí)候需要做出改變融蹂。
對(duì)于復(fù)雜的應(yīng)用來說旺订,其架構(gòu)肯定不是只有純MVP或者純MVVM這么簡單的。如果一個(gè)應(yīng)用混合了MVVM超燃、MVP和MVC区拳,那么他也變成了MVC——因?yàn)樗苯釉L問了Model層。但是如果細(xì)分來看意乓,只有訪問了Model層的那一部分才是MVC模式樱调。
模式,是人們對(duì)于某個(gè)解決方案的描述届良。在一段代碼中可能有各種各樣的設(shè)計(jì)模式笆凌,更何況是架構(gòu)。