设计模式在JavaScript中的应用(1)

本文探讨了MVC模式在JavaScript开发中的实践,通过一个内容管理系统案例,展示了如何利用MVC模式分离视图、模型和控制器,提高程序的健壮性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:Truly
日期:2007.7.24

前言

上篇文章我们介绍了在JavaScript中使用面向对象的方法,本文我们则讨论软件工程领域的另一个革新--设计模式在JavaScript中的应用。
模式的概念诞生于20世纪70年代,最初用于描述建筑领域的一些特定问题的解决方案。后来这一方案也被应用到软件开发这一领域。在我们使用Java或C++构建大型应用程序的时候,我们几乎无法离开设计模式,并且,在这些领域有着丰富的设计模式文化。微软最近也极大的推动了.Net框架下设计模式的应用,时至今日,您可能已经在上述领域积聚了相当丰富的理论和实践经验,然而,本文要讨论的是如何在JavaScript程序设计中使用模式。同时,为了不使这篇文章满是学究味,我决定用一个项目作为示例进行讨论。

目录

开始
介绍
MVC模式

开始

正如我上篇文章《在JavaScript中使用面向对象》一文中讲的,这一语言的特点就是简单宽松。所以对于JavaScript,即使您不使用面向对象、设计模式,一样可以开发出可用的程序来。但是如果采用了设计模式,无疑您的程序将具有更好的健壮性,可维护性以及易读性。所以,作为能工巧匠的您,也一定不会放过另程序蓬荜生辉的机会。

介绍

这里首先要介绍一下作为演示的项目,这是一个内容管理系统,用来管理公司的新闻内容,通过后台文章在线编辑,自动发布到公司的几个网站上面。为了获取更好的性能和简便的发布方式,我们最终决定使用静态HTML作为文章呈现载体。讲到这里,也许您已经想到动态生成HTML的方案,但是这样会有几个重要弊端,比如网站改版则需要重新生成所有的HTML页面,而且要占据相当大的磁盘空间,同时生成模板的修改也是对美工的一大考验,最终整个过程变得非常复杂,那么有什么好的解决方案吗,答案是肯定的。现在该向大家介绍今天的主角了:MVC(模型视图控制器)模式。这一设计模式被我们反复使用了多年,但是,今天我们还是再一次回顾一下MVC的知识:

MVC模式

MVC
,英文全名是Model/View/Controller,这一模式常被用来构建用户界面,最典型的作用就是将程序与用户交互的部分进行分离,可能覆盖应用的所有层或者跨越多个层,比如经典书籍《设计模式--可复用面向对象软件的基础》一书中对文字处理程序界面的处理。MVC中最重要的一点是视图不应该与模型相互通信,例如当用户操纵页面上的一个按钮时,不应该直接与数据模型进行交互,而应该将这一交互过程通过控制器来完成。整个过程如下:

MVC模式图

如果用户想从模型中获取某个数据并显示在页面上,那么这一请求必须通过控制器来完成,获取数据并更新视图。这样的好处就在于视图和模型间出于松散耦合,他们之间没有直接联系,不需要了解对方的内部实现。MVC的好处在Web应用中表现更为明显,对从事Web程序开发的人而言,分离视图和模型几乎是非常自然的。MVC的应用司空可见,例如ASP.NET 1.x时代的code-behind和2.0时代的code-beside等等,我们总是把视图写在aspx页面,而把代码写到cs文件中。当然,也有人将MVC定义的更为苛刻,他们将所有产生的HTML的代码和相关代码都归结为视图。但是无论从广义还是狭义上,在Web应用程序的开发中,MVC都得以普遍应用。

好了,现在回到我们的项目上来讲,同样是出于性能的考虑,我们决定将数据和图片保存在与前端Web Server不同的一台服务器上,以分散前端服务器的压力,但是由于XML跨域的安全限制,使得我们最终放弃了XML作为数据载体。那么作为XML最好的替代品正是JSON,恐怕找不到比它更为合适的了,而且使用JSON带来的好处远远超过XML所能给项目带来的好处。如果您长期从事Web 2.0技术研究,那么这应该是您非常熟悉的一种技术了。如果您还不够了解JSON,那么您可以通过阅读我的《深入浅出JSON》一文对其进行了解。

基本技术确定下来后,下面最终的解决方案架构图:

 pic2.JPG

至此整个系统已经一目了然了,接下来就是实施过程,对于内容管理系统,除了后台的文章编辑和分类管理等,对于每个网站的前台显示我们需要2大功能,一个是新闻列表,一个是新闻查看,这里我们需要编写几个js文件,并且分别对应不同的HTML页面,所有相关的文件分别如下:

控制器
base.js  //存放了一些服务器配置列表配置等信息
loadlist.js
loaddata.js

视图
list.html
detail.html

数据模型
list_xx.js // 文章列表的数据
xx.js  //文章详细内容的详细数据

首先我们定义了文章的数据模型:

                         article.gif

与数据库对应,这个模型包含了文章的几乎所有需要用于显示的详细数据,并最终映射为JSON数据类型,封装生成到文章对应的js数据文件19.js,参见下面代码:

var  Article = {
" ID " : 19 ,
" CatalogID " : 10 ,
" CatalogID " : 1 ,
" Title " : " Hello world! " ,
" Content " : " Welcome to learn the JavaScript patterns. " ,
" Author " : " Truly " ,
" IsEssential " : false ,
" CreateDate " : new  Date('Wed,  25  Jul  2007   09 : 59 : 00  GMT + 8 '),
" PublishDate " : new  Date('Wed,  25  Jul  2007   09 : 59 : 21  GMT + 8 '),
" UpdateDate " : new  Date('Wed,  25  Jul  2007   09 : 59 : 21  GMT + 8 '),
" ExpireDate " : new  Date('Mon,  01  Jan  2046   00 : 00 : 00  GMT + 8 '),
" PageIndex " : 1 ,
" PageCount " : 1
" Status " : 1 ,
}

然后是为列表设计的两个数据模型:ArticleListArticleListInfo,见下图:

                       articlelist.gif                              articlelistinfo.gif

这两个数据模型都是为文章列表服务的,所以我们将其合并生成到同一个js文件中,比如这个列表是罗列CatalogID=10的所有文章,list_22.js:

None.gif var  ArticleList = [
ExpandedBlockStart.gifContractedBlock.gif                
dot.gif {"ID":"866","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title1","PublishDate":new Date('Thu, 19 Jul 2007 11:18:00 GMT+8')}
ExpandedBlockStart.gifContractedBlock.gif                ,
dot.gif {"ID":"864","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title2","PublishDate":new Date('Thu, 19 Jul 2007 11:12:00 GMT+8')}
ExpandedBlockStart.gifContractedBlock.gif                ,
dot.gif {"ID":"836","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title3","PublishDate":new Date('Wed, 18 Jul 2007 10:35:00 GMT+8')}
ExpandedBlockStart.gifContractedBlock.gif                ,
dot.gif {"ID":"817","WebSiteID":"10","CatalogID":"42","IsEssential":false,"Author":"Truly","Title":"Title4","PublishDate":new Date('Mon, 16 Jul 2007 17:17:00 GMT+8')}
None.gif                    ]
ExpandedBlockStart.gifContractedBlock.gif
var  ArticleListInfo  =   dot.gif {"ArticleListID":44,"ArticleListType":2,"ArticleListNum":20,"PageIndex":1,"PageSize":20,"PageCount":1}

为了演示需要,我为代码适当增加了空格换行等控制字符,正常情况下我们不会在生成js的时候增加换行递进等控制符。

根据JavaScript的特性我们将一些配置信息定义为全局变量存放在base.js文件中,方便以后的部署维护工作。
base.js

/* ***********************************************************************
* Author: Truly
* Email:  zhuleipro#hotmail.com
* WEB:    http://truly.cnblogs.com
* Date:   2007.7.11
* Note:   This file including some Chinese, 
*         Please save as utf-8 encoding.
***********************************************************************
*/

/* ******************** 配置区开始 ******************* */

//  js数据服务器
var  GLOBAL_DATAHOST  =  'http://data.domain.com/list/';

//  新闻完整列表ID常量
var  OFFICIALNEWS  =   33 ;
var  SYSTEMANNOUNCEMENT  =   34 ;
var  ACTIVITYNEWS  =   35 ;
var  OTHERNEWS  =   36 ;

dot.gif.

最后,我们编写控制器代码:

/* ***********************************************************
* Author: Truly
* Email:  zhuleipro#hotmail.com
* WEB:    http://truly.cnblogs.com
* Date:   2007.7.11
*
************************************************************
*/

//  显示列表
function  RunShowMethod()
{
    
var  iPageCount  =  ArticleListInfo.PageCount;
    
var  iPageSize  =  ArticleListInfo.PageSize;
    
var  iPageIndex  =  ArticleListInfo.PageIndex;
    
    
var  strNewIcon = "" ;    
    
var  count  =  ArticleList.length;
    
var  arr  =   new  Array(count);

    
for  ( var  i = 0 ;i < count; i ++ )
    {    
        
if ( typeof  ArticleList[i].ATPublishDate  ==   " string " )
            dt 
=   new  Date(Date.parse(ArticleList[i].ATPublishDate));
        
else
            dt 
=  Date.parse(ArticleList[i].ATPublishDate);
        
        
//  列表模版      
        arr[i]  =   " <tr>\
                     <td>&nbsp;</td>\
                     <td><img src=\
" images / icon.jpg\ "  width=\ " 12 \ "  height=\ " 15 \ " ></td>\
                     <td><a href='
"   +  strCommunionDetailPage  +   " ?id= "   +  ArticleList[i].ATID  +   " &catID= "   +  ArticleList[i].ATCategoryID  +   " ' target='_blank' class='S9pt'> "   +  ArticleList[i].ATTitle  +   " </a></td>\
                     <td>
"    +  dt.toString()  +    " </td>\
                     <td>&nbsp;</td>\
                   </tr>
" ;
        
//  分隔行模版
         if (i  !=  count  - 1  )
            arr[i]
+= " <tr><td colspan=\ " 5 \ "  nowrap align=\ " center\ " ><hr></td></tr> " ;
    }
    
    
//  显示列表内容
    
    $(
" lbContent " ).innerHTML  =   " <table width=\ " 100 % \ "  border=\ " 0 \ "  cellspacing=\ " 0 \ "  cellpadding=\ " 0 \ " >\
                                    <tr><td height=\
" 5 \ " ></td></tr> "
                                         
+  arr.join( "" +   "  \
                                    <tr><td height=\
" 10 \ " ></td></tr>\
                                </table>
" ;    
    
//  显示分页导航条
    $('pager').innerHTML  =  getPagerHTML(getParm( " page " ), iPageCount);
 }
//  获取分页HTML代码
function  getPagerHTML(pageIndex, pageCount, group)
{
    
//  如果只有1页,不再生成页码
     if (pageCount  ==   1 return   "" ;    
    
if ( ! pageIndex) pageIndex  =   1 ;    
    strPager 
=   ""
    
if ( ! group ) group  =   10 ;
    
    
var  m  =  Math.floor(group  /   2 +   1 ;
    
var  start  =  pageIndex  -  m  + 1
    
if (start  <   1 ) start  =   1 ;
    
var  end  =  group + start - 1 ;
    
if (end  >  pageCount) end  =  pageCount;
    
if (end  -  start  <  group)
    {
        start 
=  end  -  group  +   1 ;
        
if (start  <   1 ) start  =   1 ;
    }
    
    
for ( var  i = start;i <= end;i ++ )
    {
        
if (i  ==  pageIndex)
            strPager 
+=   " &nbsp;<span class=\ " STYLE13\ "  ><font color=\ " #d20d67\ " ><b> "   +  i  +   " </b></font></span> " ;
        
else
            strPager 
+=   " &nbsp;<a href=' " + PageName + " ?page= "   +  i  +   " ' class='Blue9pt'> "   +  i  +   " </a> " ;
    }
    
    
if (pageIndex  >   1 )
        strPager 
=   " <a href=' " + PageName + " ?id= "   +  listID  +   " &page= "   +  (pageIndex  - 1 +   " ' class='Blue9pt'>上一页</a>  "   +  strPager;
    
else
        strPager 
=   " <font class='Blue9pt'>上一页</font>  "   +  strPager;
        
    
if (pageIndex  <  pageCount)
        strPager 
+=   " &nbsp;&nbsp;<a href=' " + PageName + " ?id= "   +  listID  +   " &page= "   +  (parseInt(pageIndex) + 1 +   " ' class='Blue9pt'>下一页</a> " ;
    
else
        strPager 
+=   " &nbsp;&nbsp;<font class='Blue9pt'>下一页</font> " ;
        
    
return  strPager;
}

这些控制器依赖HTML页面上的一些容器ID,例如lbTitle,lbContent,lbCreateDate等,最后在还需要HTML适当位置增加一些加载语句,或者在控制器中处理文档的onload事件加载。例如:

     var  tet  =  document.createElement(' < SCRIPT > ');
    tet.src 
=  jsToLoaded[LoadedCount]  +   " ?tmp= "   +  Math.random();
    document.body.appendChild(tet);

同时结合JavaScript文件加载,我们在数据js中还增加了主动更新语句:

if ( typeof (RunShowMethod)  ==   " function " ) RunShowMethod();

这样当页面加载结束的时候,可以主动去填充到HTML中。

我们后面讲Observe模式的时候,再详细讨论这个,还有部分函数我没有放出源码,大家可以自己进行完善,这个系统是支持文章分页的。

通过上面的具体分析,我们可以看出,在使用了MVC模式后,整个系统变得简单明了。通常,情况下数据模型不需要进行任何调整,如果出现网站改版需要,只需调整对应的list.html和detail.htm2个页面就可令这个网站全面更新,同时数据的独立存放,也极大的减少了重复HTML代码造成的空间浪费。

至此,我们的第一个模式MVC的讨论就结束了。这个方案为我们很好的演示了MVC在实际项目中的应用,但是需要注意的是它的一些缺陷。


接下来是Observe、Facade、Adapter、Singleton等模式在JavaScript中的应用,敬请期待。

to be continued....

转载于:https://www.cnblogs.com/Truly/archive/2007/07/24/830102.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值