前端MVC变形记

背景:

MVC是一种架构设计模式,它通过关注点分离鼓励改进应用程序组织。在过去,MVC被大量用于构建桌面和服务器端应用程序,如今Web应用程序的开发已经越来越向传统应用软件开发靠拢,Web和应用之间的界限也进一步模糊。传统编程语言中的设计模式也在慢慢地融入Web前端开发。由于前端开发的环境特性,在经典MVC模式上也引申出了诸多MV*模式,被实现到各个Javascript框架中都有多少的衍变。在研究MV*模式和各框架的过程中,却是“剪不断、理还乱”:

  1. 为什么每个地方讲的MVC都不太一样?
  2. MVP、MVVM的出现是要解决什么问题?
  3. 为什么有人义正言辞的说“MVC在Web前端开发中根本无法使用”?

带着十万个为什么去翻阅很多资料,但是看起来像view、model、controller、解耦、监听、通知、主动、被动、注册、绑定、渲染等各种术语的排列组合,像汪峰的歌词似的。本篇希望用通俗易懂的方式阐述清楚一些关系,由于接触时间有限,英文阅读能力有限,可能会存在误解,欢迎讨论和纠正。

MVC变形记

MVC历史

MVC最初是在研究Smalltalk-80(1979年)期间设计出来的,恐怕没有一本书能够回到计算机石器时代介绍一下Smalltalk的代码是如何实现MVC的,不仅如此,连想搞清楚当时的应用场景都很难了,都要追溯到80后出生以前的事了。但是当时的图形界面少之又少,施乐公司正在研发友好的用户图形界面,以取代电脑屏幕上那些拒人于千里之外的命令行和DOS提示符。那时计算机世界天地混沌,浑然一体,然后出现了一个创世者,将现实世界抽象出模型形成model,将人机交互从应用逻辑中分离形成view,然后就有了空气、水、鸡啊、蛋什么的。在1995年出版的《设计模式:可复用面向对象软件的基础》对MVC进行了深入的阐述,在推广使用方面发挥了重要作用。

MVC包括三类对象,将他们分离以提高灵活性和复用性。

  • 模型model用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,会有一个或多个视图监听此模型。一旦模型的数据发生变化,模型将通知有关的视图。
  • 视图view是它在屏幕上的表示,描绘的是model的当前状态。当模型的数据发生变化,视图相应地得到刷新自己的机会。
  • 控制器controller定义用户界面对用户输入的响应方式,起到不同层面间的组织作用,用于控制应用程序的流程,它处理用户的行为和数据model上的改变。 

经典MVC模式

实线:方法调用 虚线:事件通知

其中涉及两种设计模式:

  • view和model之间的观察者模式,view观察model,事先在此model上注册,以便view可以了解在数据model上发生的改变。
  • view和controller之间的策略模式

一个策略是一个表述算法的对象,MVC允许在不改变视图外观的情况下改变视图对用户输入的响应方式。例如,你可能希望改变视图对键盘的响应方式,或希望使用弹出菜单而不是原来的命令键方式。MVC将响应机制封装在controller对象中。存在着一个controller的类层次结构,使得可以方便地对原有的controller做适当改变而创建新的controller。

view使用controller子类的实例来实现一个特定的响应策略。要实现不同的响应的策略只要用不同种类的controller实例替换即可。甚至可以在运行时刻通过改变view的controller来改变用户输入的响应方式。例如,一个view可以被禁止接受任何输入,只需给他一个忽略输入事件的controller。

好吧,如果被上述言论绕昏了,请继续研读《设计模式:可复用面向对象软件的基础》。

MVC for JAVASCRIPT

我们回顾了经典的MVC,接下来讲到的MVC主要是在Javascript上的实现。

javascript MVC模式

源图

如图所示,view承接了部分controller的功能,负责处理用户输入,但是不必了解下一步做什么。它依赖于一个controller为她做决定或处理用户事件。事实上,前端的view已经具备了独立处理用户事件的能力,如果每个事件都要流经controller,势必增加复杂性。同时,view也可以委托controller处理model的更改。model数据变化后通知view进行更新,显示给用户。这个过程是一个圆,一个循环的过程。

这种从经典MVC到Javascript
MVC的1对1转化,导致控制器的角色有点尴尬。MVC这样的结构的正确性在于,任何界面都需要面对一个用户,而controller
“是用户和系统之间的链接”。在经典MVC中,controller要做的事情多数是派发用户输入给不同的view,并且在必要的时候从view中获取用户输入来更改model,而Web以及绝大多数现在的UI系统中,controller的职责已经被系统实现了。由于某种原因,控制器和视图的分界线越来越模糊,也有认为,view启动了action理论上应该把view归属于controller。比如在Backbone中,Backbone.View和Backbone.Router一起承担了controller的责任。这就为MVC中controller的衍变埋下了伏笔。

MVP

MVP(model-view-Presenter)是经典MVC设计模式的一种衍生模式,是在1990年代Taligent公司创造的,一个用于C++ CommonPoint的模型。背景上不再考证,直接上图看一下与MVC的不同。

MVP模式

经典MVC中,一对controller-view捆绑起来表示一个ui组件,controller直接接受用户输入,并将输入转为相应命令来调用model的接口,对model的状态进行修改,最后通过观察者模式对view进行重新渲染。

进化为MVP的切入点是修改controller-view的捆绑关系,为了解决controller-view的捆绑关系,将进行改造,使view不仅拥有UI组件的结构,还拥有处理用户事件的能力,这样就能将controller独立出来。为了对用户事件进行统一管理,view只负责将用户产生的事件传递给controller,由controller来统一处理,这样的好处是多个view可共用同一个controller。此时的controller也由组件级别上升到了应用级别,然而更新view的方式仍然与经典MVC一样:通过Presenter更新model,通过观察者模式更新view。

另一个显而易见的不同在于,MVC是一个圆,一个循环的过程,但MVP不是,依赖Presenter作为核心,负责从model中拿数据,填充到view中。常见的MVP的实现是被动视图(passive

view),Presenter观察model,不再是view观察model,一旦model发生变化,就会更新view。Presenter有效地绑定了model到view。view暴露了setters接口以便Presenter可以设置数据。对于这种被动视图的结构,没有直接数据绑定的概念。但是他的好处是在view和model直接提供更清晰的分离。但是由于缺乏数据绑定支持,意味着不得不单独关注某个任务。在MVP里,应用程序的逻辑主要在Presenter来实现,其中的view是很薄的一层。

MVVM

MVVM,Model-View-ViewModel,最初是由微软在使用Windows Presentation
Foundation和SilverLight时定义的,2005年John Grossman在一篇关于Avalon(WPF
的代号)的博客文章中正式宣布了它的存在。如果你用过Visual Studio, 新建一个WPF
Application,然后在“设计”中拖进去一个控件、双击后在“代码”中写事件处理函数、或者绑定数据源。就对这个MVVM有点感觉了。比如VS自动生成的如下代码:


  1. <GroupBox Header="绑定对象"> 
  2.     <StackPanel Orientation="Horizontal" Name="stackPanel1"> 
  3.         <TextBlock Text="学号:"/> 
  4.         <TextBlock Text="{Binding Path=StudentID}"/> 
  5.         <TextBlock Text="姓名:"/> 
  6.         <TextBlock Text="{Binding Path=Name}"/> 
  7.         <TextBlock Text="入学日期:"/> 
  8.         <TextBlock Text="{Binding Path=EntryDate, StringFormat=yyyy-MM-dd}"/> 
  9.         <TextBlock Text="学分:"/> 
  10.         <TextBlock Text="{Binding Path=Credit}"/> 
  11.     </StackPanel> 
  12. </GroupBox>  

  1. stackPanel1.DataContext = new Student() { 
  2.     StudentID=20130501, 
  3.     Name="张三", 
  4.     EntryDate=DateTime.Parse("2013-09-01"), 
  5.     Credit=0.0 
  6. }; 

其中最重要的特性之一就是数据绑定,Data-binding。没有前后端分离,一个开发人员全搞定,一只手抓业务逻辑、一只手抓数据访问,顺带手拖放几个UI控件,绑定数据源到某个对象或某张表,一步到位。

背景介绍完毕,再来看一下理论图

MVVM模式

首先,view和model是不知道彼此存在的,同MVP一样,将view和model清晰地分离开来。
其次,view是对viewmodel的外在显示,与viewmodel保持同步,viewmodel对象可以看作是view的上下文。view绑定到viewmodel的属性上,如果viewmodel中的属性值变化了,这些新值通过数据绑定会自动传递给view。反过来viewmodel会暴露model中的数据和特定状态给view。

所以,view不知道model的存在,viewmodel和model也觉察不到view。事实上,model也完全忽略viewmodel和view的存在。这是一个非常松散耦合的设计。

流行的MV*框架:

每个框架都有自己的特性,这里主要讨论MVC三个角色的责任。粗浅地过一遍每个框架的代码结构和风格。

BackboneJS

Backbone通过提供模型Model、集合Collection、视图View赋予了Web应用程序分层结构,其中模型包含领域数据和自定义事件;集合Colection是模型的有序或无序集合,带有丰富的可枚举API;
视图可以声明事件处理函数。最终将模型、集合、视图与服务端的RESTful JSON接口连接。

Backbone在升级的过程中,去掉了controller,由view和router代替controller,view集中处理了用户事件(如click,keypress等)、渲染HTML模板、与模型数据的交互。Backbone的model没有与UI视图数据绑定,而是需要在view中自行操作DOM来更新或读取UI数据。Router为客户端路由提供了许多方法,并能连接到指定的动作(actions)和事件(events)。

Backbone是一个小巧灵活的库,只是帮你实现一个MVC模式的框架,更多的还需要自己去实现。适合有一定Web基础,喜欢原生JS去操作DOM(因为没有数据绑定)的开发人员。为什么称它为库,而不是框架,不仅仅是由于仅4KB的代码,更重要的是

使用一个库,你有控制权。如果用一个框架,控制权就反转了,变成框架在控制你。库能够给予灵活和自由,但是框架强制使用某种方式,减少重复代码。这便是Backbone与Angular的区别之一了。

至于Backbone属于MV*中的哪种模式,有人认为不是MVC,有人觉得更接近于MVP,事实上,它借用多个架构模式中一些很好的概念,创建一个运行良好的灵活框架。不必拘泥于某种模式。


  1. // view: 
  2. var Appview = Backbone.View.extend({ 
  3.     // 每个view都需要一个指向DOM元素的引用,就像ER中的main属性。 
  4.     el: '#container', 
  5.  
  6.     // view中不包含html标记,有一个链接到模板的引用。 
  7.     template: _.template("<h3>Hello <%= who %></h3>"), 
  8.  
  9.     // 初始化方法 
  10.     initialize: function(){ 
  11.       this.render(); 
  12.     }, 
  13.  
  14.     // $el是一个已经缓存的jQuery对象 
  15.     render: function(){ 
  16.       this.$el.html("Hello World"); 
  17.     }, 
  18.  
  19.     // 事件绑定 
  20.     events: {'keypress #new-todo': 'createTodoOnEnter'} 
  21. }); 
  22. var appview = new Appview(); 
  23.  
  24. // model: 
  25. // 每个应用程序的核心、包含了交互数据和逻辑 
  26. // 如数据验证、getter、setter、默认值、数据初始化、数据转换 
  27. var app = {}; 
  28.  
  29. app.Todo = Backbone.model.extend({ 
  30.   defaults: { 
  31.     title: '', 
  32.     completed: false 
  33.   } 
  34. }); 
  35.  
  36. // 创建一个model实例 
  37. var todo = new app.Todo({title: 'Learn Backbone.js', completed: false}); 
  38. todo.get('title'); // "Learn Backbone.js" 
  39. todo.get('completed'); // false 
  40. todo.get('created_at'); // undefined 
  41. todo.set('created_at', Date()); 
  42. todo.get('created_at'); // "Wed Sep 12 2012 12:51:17 GMT-0400 (EDT)" 
  43.  
  44. // collection: 
  45. // model的有序集合,可以设置或获取model 
  46. // 监听集合中的数据变化,从后端获取模型数据、持久化。 
  47. app.TodoList = Backbone.Collection.extend({ 
  48.   model: app.Todo, 
  49.   localStorage: new Store("backbone-todo") 
  50. }); 
  51.  
  52. // collection实例 
  53. var todoList = new app.TodoList() 
  54. todoList.create({title: 'Learn Backbone\'s Collection'}); 
  55.  
  56. // model实例 
  57. var model = new app.Todo({title: 'Learn models', completed: true}); 
  58. todoList.add(model); 
  59. todoList.pluck('title'); 
  60. todoList.pluck('completed'); 

KnockoutJS

KnockoutJS是一个名正言顺的MVVM框架,通过简洁易读的data-bind语法,将DOM元素与viewmodel关联起来。当模型(viewmodel)状态更新时,自动更新UI界面。

viewmodel是model和view上的操作的一个连接,是一个纯粹的Javascript对象。它不是UI,没有控件和样式的概念,它也不是持久化的模型数据,它只是hold住一些用户正在编辑的数据,然后暴露出操作这些数据(增加或删除)的方法。

view是对viewmodel中数据的一个可视化的显示,view观察viewmodel,操作view时会发送命令到viewmodel,并且当viewmodel变化时更新。view和model是不了解彼此的存在的。


  1. <form data-bind="submit: addItem"> 
  2.     New item: 
  3.     <input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' /> 
  4.     <button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button> 
  5.     <p>Your items:</p> 
  6.     <select multiple="multiple" width="50" data-bind="options: items"> </select> 
  7. </form> 

  1. // viewmodel 
  2. var SimpleListmodel = function(items) { 
  3.     this.items = ko.observableArray(items); 
  4.     this.itemToAdd = ko.observable(""); 
  5.     this.addItem = function() { 
  6.         if (this.itemToAdd() != "") { 
  7.             // 把input中的值加入到items,会自动更新select控件 
  8.             this.items.push(this.itemToAdd()); 
  9.             // 清空input中的值 
  10.             this.itemToAdd(""); 
  11.         } 
  12.     // 确保这里的this一直是viewmodel 
  13.     }.bind(this); 
  14. }; 
  15.  
  16. ko.applyBindings(new SimpleListmodel(["Alpha", "Beta", "Gamma"])); 

AngularJS

AngularJS试图成为Web应用中的一种端对端的解决方案。这意味着它不只是你的Web应用中的一个小部分,而是一个完整的端对端的解决方案。这会让AngularJS在构建一个CRUD的应用时看起来很呆板,缺乏灵活性。AngularJS是为了克服HTML在构建应用上的不足而设计的。使用了不同的方法,它尝试去补足HTML本身在构建应用方面的缺陷。通过使用标识符(directives)的结构,让浏览器能够识别新的语法。例如使用双大括号语法进行数据绑定;使用ng-controller指定每个控制器负责监视视图中的哪一部分;使用ng-model,把输入数据绑定到模型中的一部分属性上。

双向数据绑定是AngularJS的另一个特性。UI控件的任何更改会立即反映到模型变量(一个方向),模型变量的任何更改都会立即反映到问候语文本中(另一方向)。AngularJS通过作用域来保持数据模型与视图界面UI的双向同步。一旦模型状态发生改变,AngularJS会立即刷新反映在视图界面中,反之亦然。

AngularJS原本是倾向于MVC,但是随着项目重构和版本升级,现在更接近MVVM。和Knockout
view中的风格类似,都像从WPF衍变过来的,只是Knockout使用了自定义属性data-bind作为绑定入口,而AngularJS对于HTML的变革更彻底,扩展HTML的语法,引入一系列的指令。

在AngularJS中,一个视图是模型通过HTML模板渲染之后的映射。这意味着,不论模型什么时候发生变化,AngularJS会实时更新结合点,随之更新视图。比如,视图组件被AngularJS用下面这个模板构建出来:


  1. <body ng-controller="PhoneListCtrl"> 
  2.     <ul> 
  3.         <li ng-repeat="phone in phones"> 
  4.             {{phone.name}} 
  5.             <p>{{phone.snippet}}</p> 
  6.         </li> 
  7.     </ul> 
  8. </body> 

在li标签里面的ng-repeat语句是一个AngularJS迭代器。包裹在phone.name和phone.snippet周围的花括号标识着数据绑定,是对应用一个数据模型的引用。当页面加载的时候,AngularJS会根据模版中的属性值,将其与数据模型中相同名字的变量绑定在一起,以确保两者的同步性。

在PhoneListCtrl控制器里面初始化了数据模型:


  1. // controller: 
  2. function PhoneListCtrl($scope) { 
  3.   // 数组中存储的对象是手机数据列表 
  4.   $scope.phones = [ 
  5.     {"name": "Nexus S", 
  6.      "snippet": "Fast just got faster with Nexus S."}, 
  7.     {"name": "Motorola XOOM with Wi-Fi", 
  8.      "snippet": "The Next, Next Generation tablet."}, 
  9.     {"name": "MOTOROLA XOOM", 
  10.      "snippet": "The Next, Next Generation tablet."} 
  11.   ]; 

尽管控制器看起来并没有什么控制的作用,但是它在这里的重要性在于,通过给定数据模型的作用域$scope,允许建立模型和视图之间的数据绑定。方法名PhoneListCtrl和body标签里面的ngcontroller指令的值相匹配。当应用启动之后,会有一个根作用域被创建出来,而控制器的作用域是根作用域的一个典型后继。这个控制器的作用域对所有

标记内部的数据绑定有效。

AngularJS的作用域理论非常重要:一个作用域可以视作模板、模型和控制器协同工作的粘接器。AngularJS使用作用域,同时还有模板中的信息,数据模型和控制器。这些可以帮助模型和视图分离,但是他们两者确实是同步的!任何对于模型的更改都会即时反映在视图上;任何在视图上的更改都会被立刻体现在模型中。

实践中的思考

我们使用的MVC框架是ER,适用于并能很方便地构建一个整站式的AJAX
Web应用。提供精简、核心的action、model和view的抽象,使得构建RIA应用变得简单可行。在使用的过程中近距离地体会到非常多方面的优秀的设计理念。也让我开始思考各个角色的转型。

让view上前线

我开始思考action(controller)这个角色。我觉得从纯粹地解耦角度来说,view和model应该是互相不知道彼此存在的,所有的事件流和对数据、UI的处理应该都流经action。但是这一点又极不现实。用户操作了一个UI,需要更新model的一个数据,就要fire到action,通过action来调用model的set方法。这样又有点麻烦,因为view中有对model的应用,可以一句代码搞定这一个数据的设置。所以,我自己设置了一个规则:如果是简单的模型数据读写可以直接在view中操作;如果要经过复杂的数据处理,必须流经action。于是,我遇到了一种怎么都偷不了懒(必须经过action)的情况:
比如有个主action main,两个子action
list、select,用户在list中的view选择一条数据添加到右侧select中。那走过的流程是这样的:

实践中的思考

  1. 子Action中的listView接受UI事件,fire到listAction中
  2. listAction继续将事件fire到mainView中,由主action来处理另外子Action的事情。
  3. mainView接收到事件、调用子Action selectAction的方法
  4. selectAction继续调用selectView的方法来完成UI的更新。

其中涉及的model的变化暂时不考虑。我在想,view既然把经典MVC中的controller接受用户事件的角色承接过来的,那如果借鉴Backbone的思想,把view作为controller的一个实现,推到战场的最前线。省掉两次action的中转传递,是不是更简单。

model驱动开发

实际开发中,常常会以view为核心,页面上需要展示什么数据,就去model中设置数据源。发生了用户事件,我会在action中更新model,然后刷新view。有时候会遗漏更新model,直到需要数据时才发现没有保存到model中。

model本身是独立的,自控制的,不依赖于view,能够同步支持多view的显示。就像linux上的应用程序通常会提供图形界面和命令行两种操作方式一样。那如果以model为核心,model驱动开发,数据在手、天下我有,以模型验证保证数据的完整性和正确性。实现数据绑定,任何对模型的更改都会在界面上反映出来。那我们只要预先写好view和model的关系映射(类似viewmodel),然后只关注模型数据,就OK了。

作者:佚名

来源:51CTO

时间: 2024-08-17 00:11:48

前端MVC变形记的相关文章

国产化新出路:中国服务器市场变形记

现实生活中,我们总喜欢将IT技术与体育竞技作比较,其实原因很简单,中国的竞技体育是近年来成功逆袭的标杆.同样作为根基十分薄弱的计算机技术,IT人往往希望中国的技术实力能与竞技体育一样,通过自身努力,配合国家支持,逐渐成长为国际社会上的领导者.从2016年上半年情势来看,服务器翻身做主的日子似乎指日可待了. 2016年上半年,合作.突围与坚守成为企业级IT行业的关键词.当中国逐渐成长为一个经济大国,我们的市场需求.技术实力和政策支持都开始影响全球IT发展.中国服务器市场变形记已经拉开帷幕-- 从宏

百度“变形记”遭遇阿里帝国拦路虎

中介交易 SEO诊断 淘宝客 云主机 技术大厅 互联网上从不缺乏热点,最近"百宝"之战可谓让上至大佬,下至草根站长都开了眼界,就差街头小巷卖粥的老妈妈没议论了,二者胜败如何,现在很难说,马总好像发完招在休息了,现在就等李总发招了,而李总的招数就是如何将百度C2C这一重磅炸弹就发射出去.国内搜索老大的"变形记"能否成功? 百度为何要做C2C 1.利益驱使:大佬有大佬的说法,"为中国电子商务做贡献,把蛋糕做大做强,任何一个负责人的公司都应为互联网的发展努力--

豆瓣变形记:从兴趣分享到生活社区

"豆瓣邀请你测试新版网站!"习惯性地登陆豆瓣,艾艾(化名)很快注意到页面右上角这条貌似不起眼的小提示. 和艾艾一样,大多数豆瓣网的铁杆用户这些天相继收到了这则测试邀请.点击链接进入,原来的旧版主页很快切换到豆瓣社区,换句话说,网站的主域名将从此属于豆瓣社区.而之前,社区只是豆瓣的四大产品之一. 豆瓣网创始人兼CEO杨勃显然非常看重这次改版,他说:"这是一个艰难但必要的决定."在春节前后的互联网空窗期,豆瓣网正悄然上演一出"变形记":从兴趣分享型网

重口益智游戏《眼球小怪变形记》体验

发挥创意组建怪物<眼球小怪变形记(Incredipede)>是一款口味微重的物理益智小游戏,游戏讲述的是一个奇异的眼球小怪Quozzle踏上拯救亲朋好友的冒险征途,它将借助自己变形的本领完成使命.发挥创意组建怪物游戏中每一关,玩家都需要控制Quozzle到达阳光照射的地方才能通关,同时还要沿路收集水果和其他物品.屏幕两侧有几个按钮,可以控制Quozzle的肌腱,当然了,每一关中这些肌腱的位置和实现的功能都不相同.眼球小怪变形记(Incredipede)眼球小怪Quozzle是有眼睛.四肢和肌肉

快时代中的慢思索 平安互联网金融变形记

TechWeb/文 小蘑菇当今的移动互联网是个唯快不破的时代,我们每天面对成千上万海量信息的更新换代,似乎顺应潮流的变才是这个时代亘古不变的真理.近日,关于互联网金融的话题越来越火热,各类理财宝宝们的收益情况成为了大家关注的焦点.追根溯源,互联网金融概念在中华大地的崛起大概可以从三马时代说起:阿里巴巴的马云,腾讯的马化腾,平安的马明哲这三匹黑马在理财产品的角逐伊始,便预示着在互联网领域与金融领域之间必将会上演历史性的剧目.快时代中的慢思索 平安互联网金融变形记(TechWeb配图)提到互联网金融

微博体《三国演义》、沪语版《变形记》、QQ版《红楼梦》……另类改写是拯救传统阅读还是将经典简单化?

几天前,上海图书馆讲座"80知友会"举行了一场读书会.以往读的多是经典名著,这次读的是一本名叫<反阅读>的另类读本. 这本197页.定价为17元的小书貌不惊人,却包含了20部文学名著.不是缩写,不是续写,而是"现代化"的另类改写:微博体<http://www.aliyun.com/zixun/aggregation/25533.html">三国演义>.寻人启事版<洛丽塔>.评书体<百年孤独>.淘宝体&l

家电下乡半年报业绩不佳手机下乡变形记

"半年报"业绩不佳_手机下乡变形记 马晓芳 "很不好."日前,商务部公布了1-6月家电下乡产品的销量情况,但当CBN记者询问销量名列前三的某手机企业相关负责人对销量的看法时,得到了这样了一个沮丧的答案. 根据商务部统计,前6个月,下乡手机的总销量达到42.5万部,销售金额为2.27亿元.其中诺基亚.天宇朗通以及长虹成为按销售额计算的前三,当然如果将海尔集团和海尔通信的销量汇总,前三为诺基亚.海尔和天宇朗通. 记者留意到,在25家手机下乡企业中,销量超过10万部的只有

前端MVC学习总结(一)——MVC概要与angular概要、模板与数据绑定

一.前端MVC概要 1.1.库与框架的区别 框架是一个软件的半成品,在全局范围内给了大的约束.库是工具,在单点上给我们提供功能.框架是依赖库的.AngularJS是框架而jQuery则是库. 1.2.AMD与CMD 在传统的非模块化JavaScript开发中有许多问题:命名冲突.文件依赖.跨环境共享模块.性能优化.职责单一.模块的版本管理.jQuery等前端库层出不穷,前端代码日益膨胀 AMD规范及其代表:RequireJS异步模块定义(Asynchronous Module Definitio

国产剧变形记 - 涨姿势!国产神剧的前世今生

class="post_content" itemprop="articleBody"> 在中国,你给他一个<纸牌屋>的剧本,他撑死能给你拍个<棋牌室>出来.不管怎样,我们都希望国产剧能持续创新,奋勇向前,千万不要让文化输出上的阳痿,成了大国崛起的羁绊-- 国产剧变形记