WEB前後端互動(UI介面和資料內容)如何實現

天府雲創發表於2017-03-06

作為剛接觸前端的不久的童鞋,大家都會興奮於CSS和JS所帶來漂亮介面,然而,前端工程師除了UI重構外,還有非常重要的職責在正確的區域渲染出服務端的資料。畢竟,我們要構建一個大的web應用,必然不是普普通通的靜態頁面構成。通常一般情況正常流程是這樣子的:

通過對資料庫的呼叫管理進行互動。
前臺一般是對資料庫內容按照頁面程式碼控制進行撿索並展示出來形成頁面。
後臺主要是對資料庫進行管理,增、刪、改

下文將列舉將來前端工程師(確切地講就是全棧工程師)應該必備的同後端打交道的常用技能。。

(注意只適合新手入門使用,老司機可以點評補充。謝謝!)

——————————【常規做法】——————————

HTML賦值

輸出到 Element 的 value 或 data-name

<input type="hidden" value="<?php echo $user_avatar;?>" />
<div data-value="<?php echo $user_avatar;?>"></div>

渲染結果

<input type="hidden" value="https://avatars1.githubusercontent.com/u/3949015?v=3&s=40" />
<div data-avatar="https://avatars1.githubusercontent.com/u/3949015?v=3&s=40"></div>

使用 JS 獲取

$('input').val();
// http://jquery.bootcss.com/jQuery.data/
$('div').data('avatar');

優點:

不佔用全域性變數,由 JS 自由獲取。

使用建議:

適合傳遞簡單資料,也非常適合多個簡單資料與 Element 繫結關係。

<ul>
<li>nimojs <span data-userid="1" >刪除</span></li>
<li>nimo22 <span data-userid="2" >刪除</span></li>
<li>nimo33 <span data-userid="3" >刪除</span></li>
<li>nimo44 <span data-userid="4" >刪除</span></li>
<li>nimo55 <span data-userid="5" >刪除</span></li>
</ul>
<script>
$('span').on('click',function(){
  $.post('/ajax/remove/',$(this).data('userid'),function(data){
    // ...
  })
})
</script>

JS賦值

將資料填充到 <script> 的 JavaScript 變數宣告中。

<script>
var user_avatar = "<?php echo $user_avatar;?>";
// 渲染結果
// var user_avatar = "https://avatars1.githubusercontent.com/u/3949015?v=3&s=40";
</script>

或使用 Smarty 後端模板引擎:

<script>
var user_avatar = "{$user_avatar}";
</script>

優點:
傳遞資料非常方便。前端直接呼叫 user_avatar 變數使用資料。

缺點:

為了傳遞一個字串資料佔用了全域性變數 user_avatar,當有很多資料需要傳輸時則會佔用很多全域性變數。
如果返回資料存在換行將會導致JS報錯

// 渲染結果有換行符
var user_id = "https://avatars1.githubusercontent.com/u/3949015?v=3&s=40";
// Uncaught SyntaxError: Unexpected token ILLEGAL

優化:

可以通過指向的某一個變數存放所有後端返回的內容,最小程度佔用全域性變數。例:

// PHP 程式碼
var SERVER_DATA = {
  username: {$username},
  userid: {$userid},
  title: {$title}
}
// 渲染結果
var SERVER_DATA = {
  username: "NimoChu",
  userid: 1,
  title: 'F2E'
}

使用建議:

需要最快速度傳遞資料給 JS 並十分確定此資料穩定時,使用此方式。資料格式複雜的建議使用script填充JSON 或AJAX獲取JSON 方法。

script填充JSON
什麼是JSON?

填充 JSON 資料到 <script> 標籤中,前端通過 DOM 獲取 JSON字串並解析成物件。

<script type="text/template" id="data">{"username":"nimojs","userid":1}</script>
<script>
var data = JSON.parse($('#data').html());
//{username:"nimojs",userid:1}
</script>

優點:

頁面載入完成後就可以獲取到資料。不佔用全域性變數,可傳遞大量資料集合。

缺點:

資料量特別大時會導致頁面初次載入變慢。變慢並不只是檔案大小導致的,也因為伺服器查詢資料並返回合集是需要時間,可使用AJAX獲取JSON完成按需載入和載入等待。

使用建議:

適合傳遞在DOM載入完成時就需要用到的大量資料集合。例如:前端控制頁面渲染,後端將JSON資料來源填充到 <script> 由前端使用 JavaScript模板引擎進行頁面渲染。


————————【互動做法】——————————

服務端渲染

談起服務端渲染,對於動態服務而言,這個世界上跑的大多數頁面都經歷過服務端的資料渲染,介面->前端賦值->模版渲染 。這一切都在伺服器完成,我們檢視原始碼時候,可以看到完整的html程式碼,包括每個資料值。

常用的php模版有,Smarty,Blade,Mustache,如果你們團隊使用Smarty,我們可以看到一些view的檔案裡會前套Smarty的模版語言;

<div>  
{foreach $list as $item}
    <h3>{$item['name']}</h3>
    <p>{$item['desc']}</p>
{/foreach}
</div>

如果Node,js作為服務端的話,這個時候我們可以使用前端模版渲染的模組,比如ejs,doT,jade等等。

注意不同的模版可能存在不同模版語法,需要自己學習使用

AJAX

當然服務端渲染隨著單頁應用以及Restful介面的興起,Ajax逐漸成為目前前後端交流最為頻繁的方式。Ajax實際核心是XmlHttpRequest,我們通過對該物件的操作來進行非同步的資料請求。

// 一般流程
var oReq = new XMLHttpRequest();  
oReq.addEventListener("load", function(res) {  
    // your code to do something
});
oReq.open("GET", "http://www.example.org/example.txt");  
oReq.send();

實際上我們接觸到最多jQuey就有很好的封裝,比如$.ajax$.post等,如果用Angular的話我們可以用$http服務,除了這些之外,我們可以使用第三方的Ajax庫qwest等。

如果使用Ajax的話,我們不得不面臨一些問題,由於同源限制的問題,我們不得不去克服這個問題,這個時候我們可以要求服務端,設定header頭,header('Access-Control-Allow-Origin: *');或者叫設定nginx配置

add_header 'Access-Control-Allow-Origin' 'http://yourweb.com';  
add_header 'Access-Control-Allow-Credentials' 'true';  
add_header 'Access-Control-Allow-Methods' 'GET';

當然這不是最好的做法,實際現在我們也可以這麼做:

ajax -> 代理 -> API

我們可以用php的curl或者通過伺服器的配置來實現反向代理。從而達到同源的效果。

前端工程師一定要要求每次請求的資料介面一嚴格遵循基礎的資料結構要求,儘管js是弱變數型別語言,但是我們還是應該嚴格要求,是陣列,就不應該是物件,是數字就不應該是字串,這樣做有利於降低隱藏bug並且提升前後端工作效率。

JSONP

JSONP算作JSON的一種”使用模式”,可用於解決主流瀏覽器的跨域資料訪問的問題。由於CORS的支援,我們可以簡單的將資料封裝成一個js指令碼請求,當然我們在jquery中會用到。

function logResults(json){  
  console.log(json);
}
$.ajax({
  url: "https://yourapi.com/api",
  dataType: "jsonp",
  jsonpCallback: "logResults"
});

comet

聊Comet我們還得說下短輪詢,由於某些特定的業務需求,比如通知,我們需要有及時的資料更新,我們能夠想到的就是使用setInterval每隔一定時間比如10s去獲取一次請求,從而做到一些通知更新,但是這並不一種高效的做法,這會增加伺服器的請求數量。這個時候有了另外一種概念,“反向Ajax”,由伺服器進行資料推送, Comet能夠讓資訊近乎實時的被推送到頁面上,非常適合要求實時性的獲取的資料的頁面。

如圖所示,就是一個簡單的Comet模型,就是資料請求後掛起,直到有資料響應推送到客戶端,這個時候客戶端再發起一個新的連線。

這樣相對來說可以減少一定數量的請求,以及資料的及時響應,從而一定意義的實現所謂推送。

一個簡單的PHP Demo程式碼,就是我們需要這端程式碼一直執行著…

while(true) {  
    set_time_limit(0);
    echo 'data';
    flush();
    b_flush();
    sleep(3);
}

JavaScript:

function createStreamingClient(url,progress,finished){
    var xhr=new XMLHttpRequest(),received=0;
    xhr.open("get",url,true);
    xhr.onreadystatechange=function(){
        var result;
        if(xhr.readyState==3){
            result=xhr.responseText.substring(received);
            received+=result.length;
            progress(result);
        }else if(xhr.readyState==4){
             finished(xhr.responseText);
        }
    };
    xhr.send(null);
    return xhr;
}

//  var client = createStreamingClient(url,fuc1,func2)

SSE Server-Sent Events

SSE是圍繞只讀Comet互動推出的API或者模式。SSE API用於建立到伺服器的單向連線,伺服器通過這個連線可以傳送任意數量的資料。伺服器響應的MIME型別必須是text/event-stream,而且是瀏覽器中的Javascript API能解析的格式輸出。

現對於Comet,我們可以看出我們只進行了一次連線,然後服務端會去控制資料的響應,從而傳送給客戶端。這樣相對來說,但是如同定義的描述,這種只適合只讀資料的情形。比如一些通知和狀態碼這樣的。SEE的使用非常簡單,只需要掌握這幾個api就行:

var es = new EventSource('http://your.api.com'  
function listener(event) {  
    console.log(event.data);
}
// 建立一個SSE連線
es.addEventListener("open", listener);  
// 響應獲取訊息的事件
es.addEventListener("message", listener);  
// 發生錯誤
es.addEventListener("error", listener);

注意:如果在回話過程中遇見錯誤後,預設程式會重新發起一次新的連線,從而防止掛掉就不再響應了

服務端(node,php)的程式碼,可以參考:https://github.com/Yaffle/EventSource

Web Sockets

HTML5 WebSocket 設計出來的目的就是要取代輪詢和 Comet 技術,使客戶端瀏覽器具備像 C/S 架構下桌面系統的實時通訊能力。 瀏覽器通過 JavaScript 向伺服器發出建立 WebSocket 連線的請求,連線建立以後,客戶端和伺服器端就可以通過 TCP 連線直接交換資料。也就是我們可以使用web技術構建實時性的程式比如聊天遊戲等應用。

其實Web Sockets 的API很少,就下面這些

websocket = new WebSocket("ws://your.socket.com:9001");  
// 大開
websocket.onopen = function(evt) { /* do stuff */ }; //on open event  
// 當web socket關閉
websocket.onclose = function(evt) { /* do stuff */ };  
// 進行通訊時
websocket.onmessage = function(evt) { /* do stuff */ };  
// 發生錯誤時
websocket.onerror = function(evt) { /* do stuff */ };  
// 向伺服器發傳送訊息
websocket.send(message); //send method  
websocket.close(); //close method

對於服務端的話,PHP支援了很多socket 相關api,但是我們可以使用更加成熟的框架(實用)比如phpsocket.ioRatchet.當然node.js寫 socket也非常得心應手,node.js對高併發支援相對較好,可以使用http://socket.io/

服務端大概會做下面的事情: + 建立一個socket + 繫結地址和埠 + 監聽進入的連線 + 接收新的連線 + web socket 握手 + 解碼資料

Demo教程

注意:SSE和 Web Sockets 都是新的api,需要大家考慮相容性*

小結

說了那麼多簡單總結下,大家想明白幾點就行了,客戶端與服務端誰先主動,是否強調資料的實時性。

  • AJAX – 請求 → 響應 (頻繁使用)
  • Comet – 請求 → 掛起 → 響應 (模擬服務端推送)
  • Server-Sent Events – 客戶單 ← 服務端 (服務端推送)
  • WebSockets – 客戶端 ↔ 服務端 (未來趨勢,雙工通訊)

相關文章