前言
web早已經進入了2.0時代了,如今的網頁大有往系統應用級別的方向發展的趨勢,再也不是以前的簡單展示信息的界面了。如今很多webapp已經做到了原生應用的功能,並且運用自身的優勢逐步取代之。HTML5也很給力,對多平台,多屏幕設備的良好兼容性使得前端工程師們在各種平台上大顯身手。鹵煮兩年前進公司接到的也是一個SPA應用的項目,也頗有些自己的心得,今日就寫篇博文,與大家分享下。
SPA
單頁 Web 應用 (single-page application 簡稱為 SPA) 是一種特殊的 Web 應用。它將所有的活動局限於一個Web頁面中,僅在該Web頁面初始化時加載相應的HTML、JavaScript 和 CSS。一旦頁面加載完成了,SPA不會因為用戶的操作而進行頁面的重新加載或跳轉。取而代之的是利用 JavaScript 動態的變換HTML的內容,從而實現UI與用戶的交互。由於避免了頁面的重新加載,SPA 可以提供較為流暢的用戶體驗。得益於ajax,我們可以實現無跳轉刷新,又多虧了瀏覽器的histroy機制,我們用hash的變化從而可以實現推動界面變化。從而模擬元素客戶端的單頁面切換效果:

優缺點
SPA被人追捧是有道理的,但是它也有不足之處。當然任何東西都有兩面性,以下是鹵煮總結的一些目前SPA的優缺點:
優點:
1.無刷新界面,給用戶體驗原生的應用感覺
2.節省原生(android和ios)app開發成本
3.提高發布效率,無需每次安裝更新包。這個對於ios開發人員來說印象尤其深吧。
4.容易借助其他知名平台更有利於營銷和推廣
5.符合web2.0的趨勢
缺點:
1.效果和性能確實和原生的有較大差距
2.各個瀏覽器的版本兼容性不一樣
3.業務隨着代碼量增加而增加,不利於首屏優化
4.某些平台對hash有偏見,有些甚至不支持pushstate。(你懂的)
5.不利於搜索引擎抓取
框架
一種開發模式火起來之后,對應的框架會隨之而起。像近幾年比較火的angularJS,是目前中最流行的mvvm框架,非常適合做SPA;與之類似的還有vueJS,鹵煮在項目中也用過,相對於前者比較輕量。還有早一些的backbone,提供最基本的mvc模式,其他的模塊大小和細節得自己決定。最早接觸的應該是extjs吧,這頭超級巨無霸在很早的時候被用來創建企業后台應用,如今也跟着時代的變化做出了很多的改進。等等這些框架也好,庫也好,都旨在為了更好的構建SPA應用而生的,它們優缺點鹵煮就不在此一一提了。
hash值
在此處提到一個比較重要的概念:URL中的井號。其實它只是瀏覽地址中的一個特殊符號。在以前,我們經常用它來定位文檔位置。例如以下代碼:
<a href="target">go target</a> ...... <div id="target">i am target place</div>
點擊a鏈接,文檔會滾動到id為target的div的可視區域上面去。hash除了這個功能還有另一一種含義:指導瀏覽器的行為但不上傳到服務器。大家都知道,改變url中的任何一個字符都會導致瀏覽器重新請求服務器,除了#號后面那段字符之外。所以,簡而言之我們可以這樣理解:改變#后面的值不觸發網頁重載,但會記錄到瀏覽器history中去。
實現原理
實現SPA的方法有很多,終歸一種遵循一種原則,界面無刷新。如果要實現原生應用中類似許多不同頁面切換的效果,我們采用的是div切換顯示和隱藏。而驅動div顯示隱藏的方式有很多種
1.監聽地址欄中hash變化驅動界面變化
2.用pushsate記錄瀏覽器的歷史,驅動界面發送變化
3.直接在界面用普通事件驅動界面變化
前兩種方式較為普遍,因為它們的變化記錄瀏覽器會保存在history中,可以通過回退/前進按鈕找回,或者history對象中的方法控制。最后一種方法是用普通事件驅動的,沒有改變瀏覽器的history對象,所以一旦用戶按了返回按鈕將會退到瀏覽器的主界面。所以,一般采用前兩種方式。值得一提的是,在不支持hash監聽和pushsate變化的瀏覽器中可以考慮用延時函數,不停得去監聽瀏覽器地址欄中url發生的變化,從而驅動界面變化。
從零開始
鹵煮在很久之前的一篇博文用pushstate的變化做了一個小小的示例,大家可以在之前的博文中找到它。在這里,我們用監聽hash變化的方式展示SPA是怎么樣運行工作的,同時從零開始,搭建一個基礎的SPA。幫助大家理解簡單的單界面應用的原理。
首先,我們畫出三個div,它們實際上是作為三個界面存在界面上的,body作為界面外框容器,限制着它們的大小。為了給每個界面配對一個hash地址,我們給每個div配一個id,講hash地址與對應的選擇器(id、class)建立鏈接關系,從而可以從hash變化值中操作界面。
<body> <div id="A" class="a J-A">A</div> <div id="B" class="b J-B">B</div> <div id="C" class="c J-C">C</div> </body>
接下來,為它們添加樣式,每個div都是全屏的,一開始只有A界面顯示,其他的都隱藏之:
body {
height: 500px;
width: 100%;
margin: 0;
padding: 0;
}
div {
width: 100%;
height: 100%;
position: absolute;
font-size: 500px;
text-align: center;
display: none;
}
.a {
background-color: pink;
display: block;
}
.b {
background-color: red;
}
.c {
background-color: gray;
}
現在我們給網頁添加上行為,首先需要知道的一點是,hash指即地址欄中#號后面的字符串,它的改變不會引起界面的刷新,但是會出發onhashchange事件,我們要做的就是監聽這個事件:
function hashChanged(hashObj) {
//變化之后的url
var newhash = hashObj.newURL.split('#')[1];
//變化之前的url
var oldhash = hashObj.oldURL.split('#')[1];
//將對應的hash下界面顯示和隱藏
document.getElementById(oldhash).style.display = 'none';
document.getElementById(newhash).style.display = 'block';
}
//監聽路由變化
window.onhashchange = hashChanged;
目前,只需要以上的代碼,我們便可以完成一個最簡單的SPA,通過地址欄的變化,界面會相應地變化。當然,除了手動在地址欄里面改變hash的變化,我們也可以用代碼改變它的變化,從而推動界面變化,下面是兩種方式的效果圖:


可以看到,左邊是在瀏覽器中直接修改hash引起了界面的變化,而右邊則是通過代碼控制界面變化。這兩種方式都可以在history中留下痕跡,從而當我們店家回退/前進按鈕的時候追溯到之前的界面去。有些平台不允許我們去手動修改地址欄,(比如微信),那么我們一般采用第二種方式即可。況且,比較少的用戶會去修改地址欄。
下面貼出所有的代碼:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
body {
height: 500px;
width: 100%;
margin: 0;
padding: 0;
}
div {
width: 100%;
height: 100%;
position: absolute;
font-size: 500px;
text-align: center;
display: none;
}
.a {
background-color: pink;
display: block;
}
.b {
background-color: red;
}
.c {
background-color: gray;
}
</style>
</head>
<body>
<div id="A" class="a J-A">A</div>
<div id="B" class="b J-B">B</div>
<div id="C" class="c J-C">C</div>
</body>
<script type="text/javascript">
function hashChanged(hashObj) {
//變化之后的url
var newhash = hashObj.newURL.split('#')[1];
//變化之前的url
var oldhash = hashObj.oldURL.split('#')[1];
//將對應的hash下界面顯示和隱藏
document.getElementById(oldhash).style.display = 'none';
document.getElementById(newhash).style.display = 'block';
}
//監聽路由變化
window.onhashchange = hashChanged;
</script>
</html>
SEO優化
由於我們在處理單頁應用的時候頁面是不刷新的,所以會導致我們的網頁記錄和內容很難被搜索引擎抓取到。搜索引擎抓取頁面首先要遵循http協議,可是#不是協議內的內容。而實際上也是這樣,我們沒有見過搜索引擎的搜索結果中,哪一條記錄可以快速定位到網頁內的某個位置的。解決的方法是用 #!號代替#號,因為谷歌會抓取帶有#!的URL。(Google規定,如果你希望Ajax生成的內容被瀏覽引擎讀取,那么URL中可以使用"#!"(這種URL在一般頁面一般不會產生定位效果)),這樣我們可以解決ajax的不被搜索引擎抓取的問題。在vueJs里面,我們可以看到作者就是這樣做的。
結束
以上就是利用hash原理實現的一個很簡單的SPA。當然,要實現項目中的單頁應用,還有很多工作要做。比如傳參,動畫,異步資源加載的問題都是需要解決的。鹵煮此處只是示范了一個很簡單的例子,希望在做不復雜又不想引入其他框架的同學提供一點思路。另外單頁雖好,但不要亂用哦,根據項目的具體需求制定對應的解決方案而不是一味的追潮逐流。
