本文章轉載于搜狗測試
開發網站,前端頁面需要從服務器端取數據,怎么取?你可能在想,這還不簡單,有比ajax更簡單的嗎?
jquery把此過程封裝到一個接口函數$.ajax(…)(以及幾個糖方法$.get、$.post)里,貌似已經很好用了,難道還能更好用嗎?!
我們來仔細分析一下這個事情,看我們的工作量能不能減少。我們需要從服務器取數據,就在前端利用$.ajax構造一個ajax請求,為了發送這個請求,我們必須設置好url以及拼裝好參數。服務器端呢,處理這個url,解析這個請求傳來的參數,然后把參數傳給適當的方法來生成響應數據。
我們一次次的重復這個參數的組裝成包和拆包解包過程(在前端把參數組裝成包,在服務器端把參數從包中解析出來),我們寫了很多代碼,就是為了調用一下服務器端一個方法,并把方法的返回值作為響應打給前端。
我們有沒有機會改進這個過程,把這些重復工作省掉。
在我們設計如何把這些重復工作省掉之前,我們先想想,前端調取后端一個方法來得到數據,最簡單能簡單到什么程度?
比如我們后端有一個類,類里有一個方法:
classuserFactroy{
functiongetUserInfoById(userId){
return{ username:’某某某’ }
}
}
前端在調用的時候,需要提供如下三個信息:1、類名;2、類里的方法名;3、傳送給方法的參數(在本例中就是userId)
類名和方法名可以用一個字符串來描述,參數嘛,調用方法時可能需要傳遞多個,故參數用一個數組表示
故前端為了獲取某id的用戶信息可以如下調用:
var result =MyCall(類名,方法名,參數列表)
本例中就是:
var userInfo=MyCall(“userFactroy”,”getUserInfoById”,[userId])
當然,由于js的單線程特性,我們不應該同步獲取函數返回結果,否則頁面就卡住了。實戰中應該使用callback或promise來取回結果。
callback版:
MyCall(“userFactroy”,”getUserInfoById”,[userId],function(userInfo){
…
})
promise版:
MyCall(“userFactroy”,”getUserInfoById”,[userId])
.then(function(userInfo){
…
})
不用我說,這樣的MyCall函數(還有服務端對應的接收代碼)大家肯定也會實現,這個思想本質上就是在前端和后端,分別寫一個【樁函數】,由它們來完成所有的參數打包以及解包過程,其他人只需要專注實現具體方法即可,本例中就是只顧安心實現getUserInfoById即可。
扯了這么多,現在開始說關鍵的了
如果本貼就是為了推廣如何寫或應該寫MyCall這樣的樁函數,那就不用寫本貼了,太小兒科了,呵呵O(∩_∩)O~
我們有更好的想法:
用過js模塊化嗎?就是AMD或CMD規范。沒用過?趕快百度查看一下,項目中趕快用呀!
這兩個規范,以及服務器端的CommonJS規范都有一個共同思想:
用require導入模塊,然后再使用,例如:
var userFactory=require(‘path/to/ userFactory’);
var userInfo=userFactory.getUserInfoById(userId);
里面的require很像python的import、c/c++的include、c#的using不是嗎?
我們就以前端使用CMD規范為例吧,這時你一般就是使用sea.js(一個實現了CMD規范的前端js模塊加載器)加載器,
當你在前端js中寫上如上代碼,seajs就會到路經path/to/userFactory處下載這個文件,最后也是在瀏覽器端運行這個getUserInfoById的函數實現代碼。除非getUserInfoById是純前端代碼,否則肯定會出錯。
我們能照樣使用這個形式,在前端調用userFactory.getUserInfoById,但這個函數卻還是在服務器端執行(比如,getUserInfoById在執行過程中,調用了查數據庫等操作),執行完后,函數的返回值依然能在前端得到嗎?
如果實現了,那這不就最大化的簡化了從服務端獲取數據的代碼量了嗎。另外,如果你服務器端是用node.js開發的話,你更會有一種前后端統一的感覺,因為后端js代碼在調用getUserInfoById函數的形式(代碼書寫的樣子)和前端是一樣(雖然本質上不一樣)
經過思考,在不干擾seajs中原本require方法使用的情況下,確實可以利用這個require來實現我們的目標,技術關鍵點就是,截獲seajs下載path/to/userFactory.js的請求(不要讓seajs真的把userFactory源碼下載下來了),應答一個自己構造的響應,這個響應是一個符合cmd規范的js模塊,并exports出所有userFatory所有方法,偽代碼大致如下:
varmoduleName =path.basename(req.originalUrl.split('?')[0],'.js');
varmodulePath = getModulePath(moduleName);
varm = require(modulePath);
varfunctionNames = [];
for(finm) {
if(typeofm[f] ==='function'){
functionNames.push(f);
}
}
res.send('define(function(require,exports,module){var_require=require("_require");'+ functionNames.map(function(f) {return'exports.'+ f +'=function(){return_require.call(null,"'+ moduleName +'","'+ f +'",arguments)}'}).join(';') +'})');
然后,我們在_require.js中實現我們打包參數的所謂【樁函數】:
define(function(require, exports, module){
varq = require('q');
module.exports =function() {
varargs = Array.prototype.slice.call(arguments);
args[2] =Array.prototype.slice.call(args[2]);
varparams = args[2];
vardefer = q.defer();
$.ajax({ url:'/call/',
type:'POST',
data: JSON.stringify(args)
})
.then(function(result) {
varerr = result.shift();
if(err) {
defer.reject(err);
}
else{
defer.resolve(result.length > 1 ? result : result[0]);
}
},function(err) {
defer.reject(err);
});
returndefer.promise;
}
});
至于,服務器的【樁函數】怎么寫,本篇就不再展開講了,本質上就是根據_require.js發送的ajax里的參數,提取類名,方法名,以及調用方法需要的參數,來調用服務器端對應類的類方法,并取回結果,然后把結果發送給瀏覽器。
小結:說道最后,取數據在底層還是使用ajax,這個你跑不脫,你肯定要利用瀏覽器提供的支持,來做你的事兒,我們改變的是獲取服務器數據的代碼書寫方法,我們利用模塊化js規范里的require,在不干擾其本來用法的情況下,讓它也成了獲取服務器端數據的方式,這樣可大大提高開發效率。獲取服務器端數據變得簡單的不能再簡單。
另外,你可能對這種調用服務器里函數的方式感覺一絲不安全,不過這是另一個問題,你的安全檢查大可在服務器端的【樁函數】里搞,呵呵O(∩_∩)O~