针对前端开发中基于ajax的复杂页面开发所面临的代码规模大,难以组织和维护,代码复用性、扩展性和适应性差等问题,本文尝试以MVC思想为基础,结合Web前端开发中内容-结构-表现-行为相分离的开发标准,提出一种将Web页面代码分为视图(View,页面静态部分,包括内容、结构、表现)、模型(Model,负责数据缓存、数据校验与本地逻辑处理、发起ajax请求)、控制器(Controller,负责用户和系统事件响应、模型和呈现器调度)、呈现器(Renderer,对视图的渲染,控制器与事件的绑定、数据搜集)的页面开发新模式,并基于此模式提出了一个开发框架原型。

摘要

针对前端开发中基于ajax的复杂页面开发所面临的代码规模大,难以组织和维护,代码复用性、扩展性和适应性差等问题,本文尝试以MVC思想为基础,结合Web前端开发中内容-结构-表现-行为相分离的开发标准,提出一种将Web页面代码分为视图(View,页面静态部分,包括内容、结构、表现)、模型(Model,负责数据缓存、数据校验与本地逻辑处理、发起ajax请求)、控制器(Controller,负责用户和系统事件响应、模型和渲染器调度)、渲染器(Renderer,对视图的渲染,控制器与事件的绑定、数据搜集)的页面开发新模式,并基于此模式提出了一个开发框架原型。

关键字:

MCRV设计模式 Javascript MVC Web开发标准

1.Web前端开发面临的问题

早期的Web页开发(Web前端开发)中,Web页面较为简单,大多数Web页面的功能仅限于用HTML和简单样式展示静态信息,或向服务器发送数据,Web页面与用户的交互较少。随着Web的发展,DHTML、CSS、javascript等技术出现,Web页不再仅限于展示静态信息,动态、交互成为Web页的主流功能之一。与此同时,Web页代码规模也变的较大,页面中的HTML、CSS、Javascript等代码往往混杂在一起,如何很好的组织这些代码,使Web客户端程序具有很好的结构,易于阅读和维护,成为Web前端开发人员面临的一个难题。在实践中,业界提出了内容(Content)-结构(Structure)-表现(Presentation)-行为(Behavior)相分离的Web页开发标准。在这种标准中,一个Web页代码可以分为如下四个部分:

内容:页面实际要传达的真正信息,包含数据、文档或者图片等。
    结构:对内容的划分,使内容更加具有逻辑性,易用性。类似页面的标题、作者、章、节、段落和列表。
    表现:用来描述内容外观,称之为“表现”,主要指CSS样式。
    行为:行为就是对内容的交互及操作效果。行为控制主要通过javascript实现。
内容-结构-表现-行为(CSPB)开发标准对Web页包含的的代码进行了分类,使代码各个部分得到很好的分离,使Web页初步具有了良好的结构。

随后,Web进入了崭新的Web2.0时代,以Gmail为代表的Web2.0应用大量出现,并且取得很大的成功。这类Web页的典型特征是用一个基于ajax技术的无刷新的页面完成大量复杂的功能。此类页面包括Web IM、Web Map、Gmail等应用,统称为one-page Web应用。同时,在企业级Web开发中,一个Web页同时完成多个复杂功能的情况也越来越多。在这些复杂Web页应用中, Javascript代码是此类富客户端应用程序的核心,负责与用户的复杂交互和页面展现。One-page Web页面javascript代码规模往往非常庞大,逻辑复杂,动则千行以上,有时甚至需要一个团队来完成一个页面的开发。如何合理组织大量的javascript代码,使之具有良好的扩展性,能够适应需求的变化,使代码易于维护,是广大Web前端开发人员面临的一大挑战。

纵观Web应用程序开发的历史,Web后端开发(PHP、J2EE、ASP.NET)与Web客户端开发的发展过程有一定的相似性,曾面临类似的问题。最初,Web程序功能简单,代码较少时,所有的控制、逻辑、UI展现代码混杂在一起;随着Web发展,程序规模变大,按原有方式开发出的程序显然不具有良好的结构,不利于大规模开发和后期维护,也不利于Web开发人员工作和角色的进一步细分,代码复用性差,瓶颈开始出现。Web后端程序设计者的解决方法是进行代码分类,将控制代码与展现代码开始分离, 于是Web后端开发从所谓的Model1发展到Model2;同时,传统桌面程序设计中的MVC(模型-视图-控制器)设计模式被引入,负责程序数据与逻辑计算的部分进一步分离出来,形成了Web后端开发中的MVC开发模式。MVC设计模式贯穿了软件工程分而治之的思想,有效解决了Web后端程序设计中的代码组织和复用问题。采用同一设计模式(MVC)也使代码更容易被他人理解,保证代码可靠性;它使Web开发工作可以细分为业务逻辑开发和UI展现开发。因此,MVC也利于团队开发。那么,MVC设计思想是否也能应用在前端开发中解决相关问题呢?既然问题具有许多相似性,笔者沿着相同的解决思路来试图解决前端开发中的代码组织问题。

2.MVC设计模式

在提出本文提出的MCRV设计模式之前,有必要对其所基于的MVC设计模式进行阐述。

MVC这个概念很早之前就被人提起[1],它代表一种设计思想。MVC是Model-View-Conroller的缩写,即模型-视图-控制器。这种开发模式将一个应用程序分为三个部分,模型(model)实现商业逻辑,提供数据;视图(view)负责向用户呈现界面和接受用户交互;控制器(controller)则负责响应用户交互请求,对用户请求进行翻译,根据不同的请求调用模型,执行商业逻辑。控制器本质上是一个调度器 (dispatcher),通过其包含的各个方法(action)来执行具体的商业逻辑。MVC各个部件以最小的耦合性协同工作,从而使程序具有良好的可扩展性和可维护性和复用性。

MVC设计模式最开始被用于桌面程序设计,典型地用于相同的数据需要不同的用户交互界面的设计场景。在经典的MVC设计思想中,控制器负责响应用户事件,根据事件类型和参数调用模型或改变视图。每个模型对应一个或者多个视图,当模型被控制器调用而得到改变时,模型向所有向其注册过的视图发送通知,视图根据从模型的中得到的信息改变外观。这种设计模式可以用图1表示。可以看出经典MVC设计模式中Controller、Model 都可以改变视图。

图1 经典MVC

基于MVC模式的Web开发(后端)一般可以用图2表示。在这种模式下,controller负责解析用户浏览器请求的URL,根据URL自动调用controller中的不同的Action响应用户请求。Action调用model,然后将model返回的数据填充到view中,view被返回给用户浏览器。Web服务端开发中典型的采用MVC模式的框架有CakePHP、Structs、Spring等等。

图2 Server端Web开发中的MVC

相对于早期的经典MVC模式,Web开发中的MVC模式存在一些变化,因为对于一个桌面应用程序而言,可以很方便地将视图注册给模型,当模型数据发生改变时,即时通知视图页面发生改变;而对于Web应用而言,即使将多个页面注册给一个模型,当模型发生变化时,模型无法主动发送消息给Web页面(因为Web应用都是基于请求/响应模式的),只有当用户请求浏览该页面时,控制器才负责调用模型数据来更新Web页面。同时,Web开发中的MVC也比经典MVC具有更小的耦合性,因为Model不再与View发生交互,因此程序的结构更加良好,使Web前端开发和业务逻辑开发相分离变的简单。

对于具有复杂交互逻辑和丰富界面的程序来说,MVC已被实践证明是一种行之有效的设计和开发思想。采用MVC设计模式的系统具有较好的结构,较低的耦合性,代码具有很好的维护性,能够适应复杂的业务逻辑和视图展现变化。它也非常适合团队开发,使开发人员分为不同的角色,专注于自己负责部分的开发。

3.MCRV设计模式

综上所述,MVC是解决具有复杂交互界面、代码规模大的应用程序面临代码组织、复用问题的有效设计模式。基于此,本文尝试基于MVC设计思想解决前端开发中的类似问题。

然而,在前端页面开发中不能直接套用MVC设计模式。因为,前端开发中的Web页面包含了HTML、CSS、Javascript等多个种类的代码,相对Web后端来说,Web页面整体就是一个负责UI展现、用户本地交互、发送服务请求的大View,与经典MVC及Web后端开发MVC模式中的View皆有不同。因此,需要具体问题具体分析。

首先对Web页面中的HTML、CSS、Javascript等代码进一步分析,明确它们的具体功能分类。根据业界提出的Web开发标准,Web页首先被分为了内容-结构-表现-行为几个部分。内容、结构、表现是页面的静态部分,主要负责UI的展现,用户操作指令(键盘、鼠标)的接受,因此,内容、结构、表现属于视图 (View)的范畴。行为主要就是javascript代码,负责对用户操作指令的响应。在复杂ajax应用中, javascript代码的功能一般包括:响应用户指令,执行数据验证/处理、执行客户端交互逻辑,向服务器发送ajax请求,接受并处理服务器返回的数据,根据数据改变UI(向页面结构填充内容数据、变换样式等)。基于模型-视图-控制器分离的思想,Javascript代码中的数据验证/处理、本地业务逻辑计算、向服务器发请求获取数据的功能,对应模型(Model)的功能;响应(翻译)用户操作指令,根据指令执行业务逻辑处理的功能,属于控制器(Controller)的功能;而接受处理过的数据,根据数据修改页面的内容/结构/样式的功能既不属于控制器的功范畴,也不属于模型的功能范畴,这部分代码因为根据数据对View进行呈现(render),可以命名为Renderer(渲染器或呈现器)。

根据前述分析,本文提出了MCRV设计模式,如图3所示。阐述如下:

M(Model):模型。完成数据验证、数据处理,执行客户端业务逻辑计算,或向服务器发起ajax请求调用服务端逻辑、接受返回的数据,将处理后的数据返回控制器。
   C(Controller):控制器。控制器响应View上的事件,根据事件调度执行模型的业务逻辑,从业务逻辑获取返回数据,调度相应的渲染器(Render)来完成界面展现。在这个过程中控制器会有数据的传递:控制器调用模型中的逻辑时会传送Renderer搜集的数据(form表单各域的name/value、其他控制参数),模型执行逻辑后返回作为执行结果的数据给控制器,控制器根据数据来调用渲染器(renderer)来完成界面呈现(rendering),呈现(rendering)就是修改页面结构、内容和样式的过程。数据传递过程可以用图4表示。
   R(Renderer):渲染器(呈现器)。渲染器被控制器调用,接受从控制器传递的数据,完成对界面的具体渲染。渲染器也负责控件(widget)的初始化,及建立Controller与具体事件的对应关系,事件发生时负责搜集View上的数据传送到Controller。
   V(View):视图。视图是用户最终看到的整个Web界面,由结构、内容、样式(表现)等静态内容共同构成。View由Renderer进行初始化渲染和修改。

图3:MCRV开发模式

图4:数据传递过程

可以看到在MCRV开发模式中,Controller处于控制中心的位置,Model完成具体的商业逻辑计算以及向后端发起ajax请求返回数据的功能。Controller与Model、Renderer之间的交互本质上数据交互的过程,它们之间存在着一个数据流,如图4所示。因此,制定Controller、Model、Renderer之间的交互接口时,数据格式定义很重要。

4.基于MCRV设计模式的Demo

下面是一个用MCRV模式来开发的Demo页面,页面的功能是用表格展示和修改用户信息。javascript使用了jQuery库。页面界面如图5所示。页面代码在程序清单1中

图5:基于MCRV设计模式的用户管理Demo

程序清单1

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv="content-type" content="text/html;charset=utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=7" />
  6. <title>MCRV 设计模式 Demo</title>
  7. <style type="text/css">
  8. /********表现*********/
  9. table{width:100%;border-collapse: collapse;}
  10. td{border: 1px solid black;padding: 2px;}
  11. #container {width:800px;margin:0px auto;}
  12. #tbUsers{margin:20px auto;}
  13. #tbUsers th{background-color: navy;color:white;text-align: center;vertical-align: middle;border:1px solid navy}
  14. #tbUsers td{text-align: center;}
  15. .editCaption{width:100px;text-align: right;}
  16. .buttonMargin{margin:0px 20px;}
  17. .buttonContainer{text-align: center;vertical-align: middle;height:50px}
  18. </style>
  19. <script src="./jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
  20. </head>
  21. <body>
  22. <!-------结构--------->
  23. <div id="container">
  24. <table id="tbUsers">
  25. <thead><th>id</th><th>姓名</th><th>年龄</th><th>修改</th></thead>
  26. <tbody/>
  27. </table>
  28. <div style="display: none;" id="dvEditPanel">
  29. <form id="frmModify" name="frmModify">
  30. <table>
  31. <tr>
  32. <td class="editCaption"> id: </td>
  33. <td><span id="spID"></span></td>
  34. </tr>
  35. <tr>
  36. <td class="editCaption"> 姓名: </td>
  37. <td><input type="text" size="20" id="txtName"/></td>
  38. </tr>
  39. <tr>
  40. <td class="editCaption"> 年龄: </td>
  41. <td><input type="text" size="20" id="txtAge"/></td>
  42. </tr>
  43. <tr>
  44. <td colspan="2" class="buttonContainer">
  45. <button id="btnSubmitModify" class="buttonMargin" type="button">提交</button>
  46. <button id="btnCancelModify" class="buttonMargin" type="button">取消</button>
  47. </td>
  48. </tr>
  49. </table>
  50. </form>
  51. </div>
  52. </div>
  53. <script>
  54. /***************************行为********************************/
  55. var UserManagerMCR;
  56. $(function()
  57. {
  58. UserManagerMCR=new MCR(UserController,UserModel,UserRenderer);
  59. });
  60. /*
  61. * MCR 三元组
  62. */
  63. function MCR(Controller,Model,Renderer)
  64. {
  65. this.controller=new Controller();
  66. this.model=new Model();
  67. this.renderer=new Renderer();
  68. thisthis.controller.model=this.model;
  69. thisthis.controller.renderer=this.renderer;
  70. thisthis.model.controller=this.controller;
  71. thisthis.renderer.controller=this.controller;
  72. if(typeof this.model.init=="function")
  73. {
  74. this.model.init();
  75. }
  76. if(typeof this.renderer.init=="function")
  77. {
  78. this.renderer.init();
  79. }
  80. if(typeof this.controller.init=="function")
  81. {
  82. this.controller.init();
  83. }
  84. }
  85. /*
  86. * 控制器
  87. */
  88. function UserController()
  89. {
  90. this.init=function()
  91. {
  92. this.initUserList();
  93. }
  94. this.initUserList=function()
  95. {
  96. var list=this.model.getUserList();
  97. this.renderer.renderUserList(list);
  98. }
  99. this.beginModify=function(data)
  100. {
  101. var user=this.model.getUserByID(data.id);
  102. this.renderer.showModifyUI(user);
  103. }
  104. //提交修改
  105. this.submitModify=function(user)
  106. {
  107. var result=this.model.modifyUser(user);
  108. if(result.success)
  109. {
  110. var list=this.model.getUserList();
  111. this.renderer.renderUIWhenSubmitModifySuccess(list);
  112. }
  113. else
  114. {
  115. alert(result.msg);
  116. }
  117. }
  118. //取消修改
  119. this.cancelModify=function()
  120. {
  121. this.renderer.hideModifyUI();
  122. }
  123. }
  124. /*
  125. * 模型
  126. */
  127. function UserModel()
  128. {
  129. //模拟的数据,实际应用中经常从服务器获取
  130. this.init=function()
  131. {
  132. this.data = [
  133. {id:0,name:"John",age:22},
  134. {id:1,name:"Tom",age:30},
  135. {id:2,name:"Tony",age:25}
  136. ];
  137. }
  138. //获得用户数据列表
  139. this.getUserList=function()
  140. {
  141. //todo ,可能ajax从后端返回
  142. return this.data;
  143. }
  144. //获得用户数据
  145. this.getUserByID=function(id)
  146. {
  147. var ix;
  148. $.each(this.data,function(i,item){if(item["id"]==id ) { iix=i; return false;}});
  149. return this.data[ix];
  150. }
  151. //修改用户数据
  152. this.modifyUser=function(user)
  153. {
  154. var result={success:true,msg:"修改成功"};
  155. //todo,验证参数user
  156. //todo,修改用户数据
  157. $.each(this.data,function(i,item)
  158. {
  159. if(item["id"]==user["id"])
  160. {
  161. item["name"]=user["name"];
  162. item["age"]=user["age"]
  163. return false;
  164. }
  165. });
  166. return result;
  167. }
  168. }
  169. /*
  170. * 渲染器
  171. */
  172. function UserRenderer()
  173. {
  174. this.init=function()
  175. {
  176. var me=this;
  177. $("#btnSubmitModify").click(function()
  178. {
  179. var user={id:$("#spID").text(),name:$("#txtName").val(),age:$("#txtAge").val()};
  180. me.controller.submitModify(user);
  181. });
  182. $("#btnCancelModify").click(function()
  183. {
  184. me.controller.cancelModify();
  185. });
  186. $("#tbUsers .modify").live("click",function()
  187. {
  188. var id=$(this).attr("uid");
  189. me.controller.beginModify({"id":id});
  190. });
  191. }
  192. this.renderUserList=function(list)
  193. {
  194. var htm=[];
  195. for(var ix=0;ix<list.length;ix++)
  196. {
  197. htm.push("<tr><td>" +list[ix]["id"]+"</td>" +"<td>"+list[ix]["name"]+"<td>"+list[ix]["age"]+"</td>"
  198. +"<td>"+"<a class='modify' href='javascript:void(0)' uid='"+list[ix]["id"]+"'>修改</a></td>"+"</tr>");
  199. }
  200. $("#tbUsers").children("tbody").html(htm.join(""));
  201. }
  202. this.showModifyUI=function(user)
  203. {
  204. $("#dvEditPanel").show();
  205. $("#spID").text(user["id"]);
  206. $("#txtName").val(user["name"]);
  207. $("#txtAge").val(user["age"]);
  208. }
  209. this.hideModifyUI=function()
  210. {
  211. document.frmModify.reset();
  212. $("#dvEditPanel").hide();
  213. }
  214. this.renderUIWhenSubmitModifySuccess=function(list)
  215. {
  216. this.hideModifyUI();
  217. this.renderUserList(list);
  218. }
  219. }
  220. </script>
  221. </body>
  222. </html>

5.基于MCRV模式的开发框架原型

5.1基本设计思想

框架是在一个特定的问题领域内,应用程序的部分设计与实现[2]。框架与设计模式通常是精密结合的。框架规定了应用的体系结构,使基于特定设计模式的开发能够复用公共代码;反之,框架强调设计复用,框架设计也基本上使用了设计模式,掌握了框架的设计模式可以快速掌握基于框架的应用程序开发。因此,使用MCRV设计模式进行前端开发时最好有一个与之配合的javascript框架。
基本设计考虑:

 Model、Controller、Renderer组成一个三元组MCR,一个MCR三元组中Controller对象、Model对象和Renderer对象是唯一的。

 Model、Controller、Renderer可以初始化和销毁

 Controller、Model、View在需要时可以透明地引用其他对象

 数据与逻辑分离,支持数据本地存储

 一个Web页面可以有多个MCR组,即可以有多个Controller、Model、Renderer三元组分别完成不同的控制、逻辑、展现。这为一个页面逻辑和交互非常复杂时,拆分为多个模块,由多人开发提供了支持。

5.2 基本对象及其关系

下面是一个基于上述考虑的基于MCRV设计模式的javascript框架原型(以下简称MCRV框架)。
框架的基本对象:MCR、Model、Controller、Renderer、Cache。如图6所示。

图6 基于MCRV模式的框架原型

 MCR:控制器(Controller)对象、模型对象(Model)和渲染器对象(Renderer)的复合对象。

-Dispose()方法:执行MCR销毁操作,避免javascript内存泄露;在页面unload时自动调用;这个方法自动调用Model、Controller和Renderer的dispose()方法

-model属性:包含的模型

-controller属性:包含的控制器
-renderer属性:包含的渲染器

 Model:模型对象。

-init()方法:执行模型初始化

-dispose()方法:执行对象销毁,释放资源

-cahce属性:数据的缓存。

 Controller:控制器对象

-init()方法:执行控制器初始化

-dispose()方法:执行对象销毁,释放资源

-model属性:调度的模型

-renderer属性:控制的渲染器

 Renderer:渲染器对象

-init()方法:执行渲染器初始化

-dispose()方法:执行对象销毁,释放资源

-controller属性:对controller的引用

 Cache:基于key-value的缓存对象

-get()方法:基于键值获取缓存数据

-set()方法:设置缓存数据

-remove()方法:删除对应某个键值的缓存

-size():缓存数量

5.3 MCRV框架与其他web其他部分的关系

基于MCRV模式的开发框架与Web页面开发其他组成部分的关系可以用图7表示。MCRV框架搭建起了js应用程序整体的结构,提供了应用程序上下文环境。MCRV框架可以与js组件库、css基础样式库和js业务逻辑组件一起作为应用程序构建的基础,并且它们之间没有依赖关系。尽管应用程序可以在js基础库(如jQuery)上开发,但是MCRV框架不依赖这些库。

图7 基于MCRV模式的开发框架与其他Web页其他部分的关系

6.结论

实践表明,MCRV设计模式能够有效解决复杂ajax开发中面临的问题,可以使Web页面代码结构良好,降低javascript代码的耦合性,提高复用性、适应性和灵活性,使Web页更加易于维护和重构。

参考文献

[1]. Trygve Reenskaug Taskon.Working with objects in the user interfaces.

[2]. J. Van Gurp, J. Bosch.Design, Implementation and Evolution of Object Oriented Frameworks Concepts and Guidelines.pdf.

【本文首发于:百度泛用户体验】http://www.baiduux.com/blog/2011/07/08/frontend-mcrv-design-pattern/
【关注百度技术沙龙】

转载于:https://blog.51cto.com/baidutech/747840

前端开发中的MCRV模式相关推荐

  1. 初学者Web介绍一些前端开发中的基本概念用到的技术

    Web开发是比较费神的,需要掌握很多很多的东西,特别是从事前端开发的朋友,需要通十行才行.今天,本文向初学者介绍一些Web开发中的基本概念和用到的技术,从A到Z总共26项,每项对应一个概念或者技术. ...

  2. 前端开发中常用设计模式-总结篇

    本文是向大家介绍前端开发中常用的设计模式,它使我们编写的代码更容易被复用,也更容易被人理解,并且保证代码的稳定可靠性. 1.什么是设计模式 通俗来讲,就是日常使用设计的一种惯性思维. 因为对应的这种思 ...

  3. 前端开发中icon图标使用的那些门道儿

    前端开发图标使用 在日常开发,前端coder绕不开一个话题,在页面上添加各种小图标,比如 或者是这样的: 一般来说,总体上有三种方案: 位图图标,png图片,经典的使用场景--精灵图: 字体图标,比较 ...

  4. 前端开发中常用图片格式

    前端开发中常用图片格式 在我们的日常开发中.必不可少会使用很多种图片. 我们需要根据业务场景来选择所使用的图片类型. 这里我整理了一些常用图片类型.他们的优缺点以及建议的使用场景. 如何在计算机中显示 ...

  5. 前端开发中的性能那点事

     前端开发中的性能那点事(一)巧用xdebug 前言: 在我们平时的php开发中,一个大的项目经过长时间的积累以后你会发现性能越来越慢,而性能到底消耗在了什么地方,常常是一个令人头疼的问题,funct ...

  6. 前端开发中的Error以及异常捕获

    本文首发于公众号:符合预期的CoyPan 写在前面 在前端项目中,由于JavaScript本身是一个弱类型语言,加上浏览器环境的复杂性,网络问题等等,很容易发生错误.做好网页错误监控,不断优化代码,提 ...

  7. java backbone_[Java教程]移动前端开发中的Backbone之一:Backbone中的模型和集合

    [Java教程]移动前端开发中的Backbone之一:Backbone中的模型和集合 0 2015-09-24 17:00:04 当我们开发含有大量Javascript的web应用程序时,首先你需要做 ...

  8. 【repost】一探前端开发中的JS调试技巧

    有请提示:文中涉及较多Gif演示动画,移动端请尽量在Wifi环境中阅读 前言:调试技巧,在任何一项技术研发中都可谓是必不可少的技能.掌握各种调试技巧,必定能在工作中起到事半功倍的效果.譬如,快速定位问 ...

  9. 浅谈开发中的MVVM模式及与MVP和MVC的区别

    2019独角兽企业重金招聘Python工程师标准>>> 我记得前段时间分享了一篇文章< 浅谈Andorid开发中的MVP模式>(点击可跳转),反响不错,为了进一步介绍MV ...

最新文章

  1. 中值定理符号怎么读_微分、微分中值定理、泰勒公式
  2. 死锁的必要条件及避免
  3. opencv获取摄像头帧率分辨率
  4. Android --- 如何使状态栏和标题栏底色相同
  5. JavaScript 中的闭包和作用域链(读书笔记)
  6. AxureRP9不同Page使用同一个Master,触发不同事件。
  7. 【NOI2016】循环之美,mobius反演+杜教筛
  8. openflow和open vSwitch简介
  9. 基于JAVA+Servlet+JSP+MYSQL的员工管理系统
  10. mysql必背_必背的mysql语句.doc
  11. C 语言结构体成员赋值的深拷贝和浅拷贝
  12. js焦点图片层叠轮播切换滚动
  13. css3中的perspective与perspective-origin
  14. clion连接mysql,使用 CLion 调试 mysql 源码
  15. Windows - 修复所有快捷方式的打开方式
  16. 读《我怎样设计飞机》
  17. PAT : PAT (Basic Level) Practice(中文)答案(1001 ~ 1095)(纯C编写)
  18. STC89C51单片机
  19. html原生js实现图片轮播,原生JS实现图片轮播切换效果
  20. 林园:为2009年培育“种子”

热门文章

  1. 17款奇骏智能互联连接成功--高德导航
  2. 小赵是某大学计算机科学,天津大学计算机科学与技术专业考研经验分享
  3. html特殊符号不显示,HTML特殊符号显示技巧
  4. 如何把闲置的手机或平板变成显示器
  5. 向日葵远程连接linux断开,win10与ubuntu18.04的向日葵远程连接问题
  6. 企业数据治理痛点与阿里巴巴数据治理方案
  7. 计算机论文文献综述 游戏,【2018最新】游戏毕业设计参考文献-word范文 (4页)
  8. 远光智能报账系统荣获“2021年度优秀软件产品”
  9. php织梦二次开发的功能例子,织梦dedecms二次开发php使用json讲解
  10. 云合同的文件如何存放到服务器,数据迁移到云服务器合同