先看事例,根据下面的HTML和后端接口来更新界面。
HTML:
<ul> <li>用户名:<span id="userName"></span></li> <li>昵称:<span id="loveName"></span></li> <li>生日:<span id="birthday"></span></li> </ul> <input type="button" value="更新" id="update" rel="123456789" />
后端接口:http://www.xxx.com/getuserinfo.php
参数:uid Number
返回数据:{"userName":"lujun", "loveName":"鲁军", "birthday":"1989/02/02"};
要求:当用户点击update 按钮,根据rel值从服务器取得用户的信息,并且显示到HTML对应的位置
不用思考,你的代码应该是这样的(利用jQuery):
$( " #update " ).click( function (){
var uid = $( this ).attr( " rel " );
$.get( " http://www.xxx.com/getuserinfo.php " ,{ " uid " :uid},
function (data){
// var data = {"userName":"lujun", "loveName":"lujun", "birthday":"1989/02/02"};// 模拟数据
$( "# userName " ).text(data.userName);
$( "# loveName " ).text(data.loveName);
$( "# birthday " ).text(data.birthday);
}
);
});
对于简单的页面更新这并没有任何问题,也是最佳方案。简单,易懂
今天讨论复杂的WEB站点,页面上可能有10-20个这样的按钮。还采用这样的更新方式,你会发现
1:对于后端API调用的代码无法复用
2:页面代码javascript非常乱(逻辑更复杂了,返回数据正确定判断和提示,HTML模板解析,多级请求嵌套)
3:多人维护同一站点,看不懂别人的代码(因为都采用了自己的方式)
使用观察者模式
我们把获取数据的ajax看作一个数据 “发布者”,把更新UI界面的看作一个 “数据订阅者”。
于是有了下面的基本代码
;( function (){
// 发布者
var Model = function (key){
this .key = key;
this .observers;
}
Model.prototype = {
// 添加订阅者
addObserver: function (a){
if ( ! this .observers){ this .observers = []};
this .observers.push(a);
},
// 获取数据的通道
loadData: function (){
throw " undefined " ;
},
// 通知订阅者数据发生改变
changeValue: function (data){
var observers = this .observers;
for ( var i = 0 ; i < observers.length; i ++ ){
observers[i].update(data, this );
}
}
}
// 订阅者
var Observer = function (model){
this .models;
}
Observer.prototype = {
update: function (){
// 用来维护用户界面
throw " undefined " ;
},
// 添加发布者
addModel: function (a){
if ( ! this .models){ this .models = []};
this .models.push(a);
},
// 主动拉去信息
pull: function (){
var models = this .models;
for ( var i = 0 ; i < models.length; i ++ ){
models[i].loadData.apply(models[i], arguments);
}
}
}
window.PAGE = {
" Model " :Model,
" Observer " :Observer,
};
})();
使用 PAGE 这个命名空间保存这两个“类”。
发布者
Model {
addObserver:添加订阅者
loadData:获取服务器数据,需要重载的方法
changeValue:通知界面更新
}
订阅者
Observer{
update:更新界面,需要重载的方法
addModel:添加发布者(主要用来实现拉去信息)
pull:拉去信息(拉去信息一般都很少用)
}
有了上面的代码,那么要完成上面的题目应该这样做
// Model 关注数据传输
function getUserInfoModel(){}
getUserInfoModel.prototype = new PAGE.Model();
getUserInfoModel.prototype.loadData = function (uid){
var _this = this ;
$.get( " http://www.xxx.com/getuserinfo.php " ,{ " uid " :uid},
function (data){
// var data = {"userName":"lujun", "loveName":"鲁军", "birthday":"1989/02/02"};//模拟数据
_this.changeValue(data);
}
);
};
// Observer 关注用户界面
function updateUserInfo(){}
updateUserInfo.prototype = new PAGE.Observer();
updateUserInfo.prototype.update = function (data){
$( " #userName " ).text(data.userName);
$( " #loveName " ).text(data.loveName);
$( " #birthday " ).text(data.birthday);
}
// 关注UI交互
$( " #update " ).click( function (){
var uid = $( this ).attr( " rel " );
var model = new getUserInfoModel();
var observer = new updateUserInfo();
model.addObserver(observer);
model.loadData(uid);
});
获取数据和页面更新 分离了,如果再有其他地方需要获取用户信息,可以直接 new getUserInfoModel().loadData(),不必要在写一次javascript请求(这很重要)。
如果团队约定每次更新数据,都按照这样的方式来工作。无论你看谁的代码,逻辑始终那么清晰。他调用了一个API,更新了页面上的这些内容。
工作也会变得傻瓜式,首先 new 出 发布者和观察者,添加关注并覆盖获取数据,和更新界面的方法。大功告成
不难发现,上面的代码任然存在一些问题。比如他增加了 2 个全局变量 getUserInfoModel ,updateUserInfo。这样绝对不行,不可能每一个API的调用都添加两个全局变量。
使用单体模式
单体(singleton)模式是javascript中最基本最有用的模式之一,它可能比任何模式都常用。《javascript设计模式》 P65。(如果不太了解可以查阅下资料)
单体可以用来划分命名空间,减少全局变量,使代码更容易阅读和维护。
回到上面的代码,getUserInfoModel ,updateUserInfo 继承了 Model , Observer。他们的主要工作 覆盖 loadData,update。并且提供一个全局可访问的名字。
使用单体可以这样做:
// 依然使用PAGE 这个命名空间。
PAGE.loadDatas = {}; // 存放所有数据更新的方法
PAGE.updates = {}; // 存放所有页面的更新方法
PAGE.loadDatas.getUserInfo = function (uid){
var _this = this ;
$.get( " http://www.xxx.com/getuserinfo.php " ,{ " uid " :uid},
function (data){
var data = { " userName " : " lujun " , " loveName " : " 鲁军 " , " birthday " : " 1989/02/02 " }; // 模拟数据
_this.changeValue(data);
}
);
}
PAGE.updates.updateUserInfo = function (data){
$( " #userName " ).text(data.userName);
$( " #loveName " ).text(data.loveName);
$( " #birthday " ).text(data.birthday);
}
$( " #update " ).click( function (){
var uid = $( this ).attr( " rel " );
var model = new PAGE.Model(); // 直接继承 Model
var observer = new PAGE.Observer(); // 直接继承 Observer
model.loadData = PAGE.loadDatas.getUserInfo; // 重载(覆盖) loadData
observer.update = PAGE.updates.updateUserInfo; // 重载(覆盖) update
model.addObserver(observer);
model.loadData(uid);
});
使用直接继承(原来是继承2次),loadData,update方法被覆盖,同时提供了全局可访问的名字。代码组织很好。出色完成。
当你写过几次以后,会发现 click 方法体内的模式几乎万年不变,你可以尝试门面模式进行一个包装。
最后:
对于多人协作,必须要找一个大家都认同的编码方式(相信你也认同这一点)。
对于javascript 使用单体 比 (function(){/**你的N多代码**/})()。好很多
观察者应该有事件注册和派发(fire)的能力。这可以解决多级AJAX请求嵌套的问题
上面讨论的方式,并不适合简单的站点(少交互)。