接上篇:
目前来说,引入前端框架已经是大势所趋了,很多时候后端的一些数据处理都转移给了前端去完成,特别是在REST模式下。
下面部分来自segment社区的内容摘选:
什么是前端框架?引入前端框架的契机是什么?
当前端从web page变成web app,就需要前端框架了,web page 以表现为主,web app以应用为主。现在我们在 web 上,已经不仅仅是去看了,我们更多的时候是去用。
前端框架的使用,让不断刷新从服务器获得静态页面的流程 变成了纯粹的客户端对服务端的请求数据-组织数据-显示数据的流程 。
1.数据模型
在这一块,我想插入一些面向对象的思想。
什么是面向对象?
面向对象编程:简称就是OOP(Object Oriented Programming)。它把对象当做程序的基本单元,一个对象包括数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。(总的宗旨就是:你办事我放心!)这也是一个很好的鉴别一个面向对象的设计是否正确的方法。一个好的面向对象设计,会让你让他办事的时候,你不得不放心(也就是说,你不放心也没用,反正你什么都不知道)。
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student:
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
这里产生一个问题:如何理解js中的数据模型?(...后面会介绍)
在这些框架里,定义数据模型的方式与以往有些差异,主要在于数据的get和set更加有意义了,比如说,可以把某个实体的get和set绑定到RESTful的服务上,这样,对某个实体的读写可以更新到数据库中。另外一个特点是,它们一般都提供一个事件,用于监控数据的变化,这个机制使得数据绑定成为可能。
在一些框架中,数据模型需要在原生的JavaScript类型上做一层封装,比如Backbone的方式是这样:
//下面是backboneJs的定义数据模型的方式; var Todo = Backbone.Model.extend({ // Default attributes for the todo item. defaults : function() { return { title : "empty todo...", order : Todos.nextOrder(), done : false }; }, // Ensure that each todo created has `title`. initialize : function() { if (!this.get("title")) { this.set({ "title" : this.defaults().title }); } }, // Toggle the 'done' state of this todo item. toggle : function() { this.save({ done : !this.get("done") }); } });
上述例子中,defaults方法用于提供模型的默认值,initialize方法用于做一些初始化工作,这两个都是约定的方法,toggle是自定义的,用于保存todo的选中状态。
数据模型也可以包含一些方法,比如自身的校验,或者跟后端的通讯、数据的存取等等,在上面例子中,也有体现一些。
AngularJS的模型定义方式与Backbone不同,可以不需要经过一层封装,直接使用原生的JavaScript简单数据、对象、数组,相对来说比较简便。
2.控制器
控制器是模型和视图之间的纽带。控制器从视图获得事件和输入,对它们进行处理(很可能包含模型),并相应地更新视图。当页面加载时,控制器会给视图添加事件监听,比如监听表单提交或按钮点击。然后,当用户和应用产生交互时,控制器中的事件触发器就开始工作了。很典型的,在controller中定义表单的提交事件或者点击事件。
下面用Jquery实现一个例子:
var Controller = {}; // 使用匿名函数来封装一个作用域 (Controller.users = function($){ var nameClick = function(){ /* ... */ }; // 在页面加载时绑定事件监听 $(function(){ $("#view .name").click(nameClick); }); })(jQuery);
上面的代码创建了user控制器,这个控制器是放在controller变量下的命名空间。然后用匿名函数封装了作用域,防止对全局作用域污染。当页面加载时,程序给视图元素绑定了点击事件的监听。
再举个Angularjs中控制器的例子:还是以上面的todo对象为例,在AngularJS中,会有一些约定的注入,比如$scope,它是控制器、模型和视图之间的桥梁。在控制器定义的时候,将$scope作为参数,然后,就可以在控制器里面为它添加模型的支持。
function TodoCtrl($scope) { $scope.todos = [{ text : 'learn angular', done : true }, { text : 'build an angular app', done : false }]; $scope.addTodo = function() { $scope.todos.push({ text : $scope.todoText, done : false }); $scope.todoText = ''; }; $scope.remaining = function() { var count = 0; angular.forEach($scope.todos, function(todo) { count += todo.done ? 0 : 1; }); return count; }; $scope.archive = function() { var oldTodos = $scope.todos; $scope.todos = []; angular.forEach(oldTodos, function(todo) { if (!todo.done) $scope.todos.push(todo); }); }; }
本例中为$scope添加了todos这个数组,addTodo,remaining和archive三个方法,然后,可以在视图中对他们进行绑定。
3.视图
对于AngularJS来说,基本不需要有额外的视图定义,它采用的是直接定义在HTML上的方式,比如:
<div ng-controller="TodoCtrl"> <span>{{remaining()}} of {{todos.length}} remaining</span> <a href="" ng-click="archive()">archive</a> <ul class="unstyled"> <li ng-repeat="todo in todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> </li> </ul> <form ng-submit="addTodo()"> <input type="text" ng-model="todoText" size="30" placeholder="add new todo here"> <input class="btn-primary" type="submit" value="add"> </form> </div>
在这个例子中,使用ng-controller注入了一个TodoCtrl的实例,然后,在TodoCtrl的$scope中附加的那些变量和方法都可以直接访问了。注意到其中的ng-repeat部分,它遍历了todos数组,然后使用其中的单个todo对象创建了一些HTML元素,把相应的值填到里面。这种做法和ng-model一样,都创造了双向绑定,即:
- 改变模型可以随时反映到界面上
- 在界面上做的操作(输入,选择等等)可以实时反映到模型里。
而且,这种绑定都会自动忽略其中可能因为空数据而引起的异常情况。
4.模板
模板是这个时期一种很典型的解决方案。来个场景:在一个界面上重复展示类似的DOM片段,例如微博,
iv class="post" ng-repeat="post in feeds"> <div class="author"> <a ng-href="/user.html?user={{post.creatorName}}">@{{post.creatorName}}</a> </div> <div>{{post.content}}</div> <div> 发布日期:{{post.postedTime | date:'medium'}} </div> </div>
5.路由
通常路由是定义在后端的,但是在这类MV*框架 的帮助下,路由可以由前端来解析执行。
AngularJS中定义路由的方式有些区别,它使用一个$routeProvider来提供路由的存取,每一个when表达式配置一条路由信息,otherwise配置默认路由,在配置路由的时候,可以指定一个额外的控制器,用于控制这条路由对应的html界面:
app.config(['$routeProvider', function($routeProvider) { $routeProvider.when('/phones', { templateUrl : 'partials/phone-list.html', controller : PhoneListCtrl }).when('/phones/:phoneId', { templateUrl : 'partials/phone-detail.html', controller : PhoneDetailCtrl }).otherwise({ redirectTo : '/phones' }); }]);
注意,在AngularJS中,路由的template并非一个完整的html文件,而是其中的一段,文件的头尾都可以不要,也可以不要那些包含的外部样式和JavaScript文件,这些在主界面中载入就可以了。
6.自定义标签
最完美的体现这个功能是angularjs.
在AngularJS的首页,可以看到这么一个区块“Create Components”,在它的演示代码里,能够看到类似的一段:
<tabs> <pane title="Localization"> ... </pane> <pane title="Pluralization"> ... </pane> </tabs>
那么它是怎么做到的呢?
在angularjs首页,我们可以看到这样一块区域“Create Components”,在他的演示代码中,可以看到这一段:
<tabs> <pane title="Localization"> ... </pane> <pane title="Pluralization"> ... </pane> </tabs>
那么它是怎么做到的呢?原因在下面:
angular.module("components",[]).directive('tabs',function( ){ return { restrict:'E', transclude :true, scope:{}, controller: function($scope,$element){ var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; } this.addPane = function(pane) { if (panes.length == 0) $scope.select(pane); panes.push(pane); } }, template : '<div class="tabbable">' + '<ul class="nav nav-tabs">' + '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">' + '<a href="" ng-click="select(pane)">{{pane.title}}</a>' + '</li>' + '</ul>' + '<div class="tab-content" ng-transclude></div>' + '</div>', replace : true }; }).directive('pane',function(){ return { require : '^tabs', restrict :'E', transclude :'true', scope :{title : '@'}, link : function(scope,elment,attrs,tabsCtrl){ tabsCtroller.addpane(scope) ; }, template :'<div class="tab-pane" ng-class="{active: selected}" ng- transclude>' + '</div>', replace :true }; });
这段代码里,定义了tabs和pane两个标签,并且限定了pane标签不能脱离tabs而单独存在,tabs的controller定义了它的行为,两者的template定义了实际生成的html,通过这种方式,开发者可以扩展出自己需要的新元素,对于使用者而言,这不会增加任何额外的负担。
以上内容大部分来自图灵社区。下篇接着本篇最后一个例子续写directive的各种属性含义以及controller之间的交互或者说通信。(写着写着不知不觉又到了angularJs...)