前端进化史(二)

接上篇:

目前来说,引入前端框架已经是大势所趋了,很多时候后端的一些数据处理都转移给了前端去完成,特别是在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...)

 

转载于:https://www.cnblogs.com/brancepeng/p/5387022.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值