第四十六课:MVC和MVVM的开发区别

实现MVC的目的就是为了让M和V相分离。前端的MVC无法做到View和Model的相分离,而MVVM可以。

我们先来看一个用MVC模式开发的经典例子:(一定要深入了解这种开发的思想,而不是看懂代码)

$(function(){
  //基本的Todo模型,
  var Todo = Backbone.Model.extend({
    // 设置模型的默认属性
    defaults: {
      content: "empty todo...",
      done: false
    },
    //确保每一个模型的content都不为空
    initialize: function() {
      if (!this.get("content")) {
        this.set({"content": this.defaults.content});
      }
    },
    // 将模型的done属性值置为反,比如,之前的done如果为true,那么调用toggle方法后,就会被置为false
    toggle: function() {
      this.save({done: !this.get("done")});
    },
    //删除模型中的数据
    clear: function() {
      this.destroy();
    }
  });
   //Todo的一个集合,数据通过localStorage存储在本地。
  var TodoList = Backbone.Collection.extend({
    // 设置Collection的模型为Todo
    model: Todo,
    //存储到本地,在todos-backbone的命名空间中
    localStorage: new Store("todos-backbone"),
    done: function() {
      return this.filter(function(todo){ return todo.get(‘done‘); });  //如果模型的done属性为true,就返回此模型
    },
    nextOrder: function() {   //得到当前集合中最后一个模型的order属性值,然后加1.
      if (!this.length) return 1;
      return this.last().get(‘order‘) + 1;
    },
    //Backbone内置函数,根据todo对象的order属性值进行排列
    comparator: function(todo) {
      return todo.get(‘order‘);
    }
  });
  // 创建一个全局的Todos的collection对象
  var Todos = new TodoList;  

  var TodoView = Backbone.View.extend({
    //下面这个标签的作用是,把template模板中获取到的html代码放到这标签中。
    tagName:  "li",
    // 一个Todo模型显示在页面上的模板
    template: _.template($(‘#item-template‘).html()),

    // 为每一个Todo模型在页面上的视图绑定事件
    events: {
      "click .check"              : "toggleDone",   //点击每一个todo模型的视图时,就会改变此模型done属性值

    "dblclick label.todo-content" : "edit",   //双击此视图时,就可以编辑里面的内容
    "click span.todo-destroy" : "clear",

      "keypress .todo-input"      : "updateOnEnter",    //在视图的内容中按下按键,就会执行undateOnEnter方法
      "blur .todo-input"          : "close"     //可以通过按下enter按键保存,也可以通过鼠标失去焦点保存
    },

    //初始化设置了TodoView和Todo的一对一引用,这里我们可以把todoview看作是todo在页面上的映射。
    initialize: function() {
      _.bindAll(this, ‘render‘, ‘close‘, ‘remove‘);
      this.model.bind(‘change‘, this.render);   //model只要一改变,就会马上在视图上显示
    },
    // 渲染todo模型中的数据到 item-template 中,把生成的html添加到view的el元素中。
    render: function() {
      $(this.el).html(this.template(this.model.toJSON()));return this;
    },
    // 改变模型的done属性值
    toggleDone: function() {
      this.model.toggle();
    },

    edit: function() {
      $(this.el).addClass("editing");
      this.input.focus();
    },

// 关闭编辑界面,并把修改内容保存到后台,这里会保存在localStorage中
    close: function() {
      this.model.save({content: this.input.val()});
      $(this.el).removeClass("editing");
    },
    // 按下回车之后,才关闭编辑界面
    updateOnEnter: function(e) {
      if (e.keyCode == 13) this.close();
    },
    // 移除对应条目,以及对应的数据对象
    clear: function() {
      this.model.clear();
    }
  }
  var AppView = Backbone.View.extend({
    //绑定页面上主要的DOM节点
    el: $("#todoapp"),
    // 在页面底部显示的统计数据模板
    statsTemplate: _.template($(‘#stats-template‘).html()),
    // 绑定dom节点上的事件
    events: {
      "keypress #new-todo":  "createOnEnter",    //按下enter键,就会新建一个todo模型,显示在页面中"click .todo-clear a": "clearCompleted",   //删除模型的done为true的view
      "click .mark-all-done": "toggleAllComplete"   //如果点击全部完成的元素,那么就把所有的model的done设置为此元素的状态,此元素选上,就设置为true,不选上,就设置为false
    },
    //在初始化过程中,绑定事件到Todos上,当任务列表改变时会触发对应的事件。最后把存在localStorage中的数据取出来。
    initialize: function() {
      //下面这个是underscore库中的方法,用来绑定方法到目前的这个对象中,是为了在以后运行环境中调用当前对象的时候能够找到对象中的这些方法。
      _.bindAll(this, ‘addOne‘, ‘addAll‘, ‘render‘, ‘toggleAllComplete‘);

      this.input = this.$("#new-todo");
      this.allCheckbox = this.$(".mark-all-done")[0];

      Todos.bind(‘add‘,     this.addOne);   //给集合绑定事件
      Todos.bind(‘reset‘,   this.addAll);   //取到数据后,就会执行addAll方法
      Todos.bind(‘all‘,     this.render);    //执行完addAll方法后,就会执行render方法,因为,集合取到数据时,会触发reset方法和all方法,但是reset方法会先执行。all事件的意思就是,只要Todos有改变,就会触发此事件

      Todos.fetch();    //从服务器请求数据,由于此集合有localStorage属性,就说明,它是从本地的取数据
    },
    render: function() {
      var done = Todos.done().length;    //从本地取到的todo模型中,有多少个的done属性是true。
this.$(‘#todo-stats‘).html(this.statsTemplate({
        total:      Todos.length,
        done:       done
      }));
      //如果全部完成了,就把页面上的checkbox勾上,如果没有,就不用勾上
      this.allCheckbox.checked = (done===Todos.length);
    },
    addOne: function(todo) {
      var view = new TodoView({model: todo});    //针对每一个todo模型,我们新建一个todoView视图,这样就实现了一个模型todo对应一个视图tudoview。
      this.$("#todo-list").append(view.render().el);   //并把每一个模型对应的视图添加到页面中
    },
    addAll: function() {
      Todos.each(this.addOne);   // 对集合中的每一个模型todo,调用addOne方法。
    },
    //生成一个新Todo的所有属性的字典
    newAttributes: function() {
      return {
        content: this.input.val(),
        order:   Todos.nextOrder(),
        done:    false
      };
    },
    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      Todos.create(this.newAttributes());  //在集合中创建一个todo模型之后会添加到集合中,这时会触发集合的add事件。在视图appview就会新建这个model的view显示在页面上
      this.input.val(‘‘);
    },
    // 去掉model的done属性值为true的view
    clearCompleted: function() {
      _.each(Todos.done(), function(todo){ todo.clear(); });
      return false;
    },//处理页面点击标记全部完成按钮
    //处理逻辑:如果标记全部按钮已选,则所有都完成,如果未选,则所有的都未完成。
    toggleAllComplete: function () {
      var done = this.allCheckbox.checked;
      Todos.each(function (todo) { todo.save({‘done‘: done}); });
    }

  });
  var App = new AppView;   //新建一个视图
});

上面的这个例子中,总共定义了一个模型Todo,一个集合TodoList,一个视图TodoView,一个视图AppView,没有定义Router路由。

这里面AppView是一个父元素,它的每一个子元素就是一个TodeView。

TodoList集合中的每一个模型就是Todo。

AppView操作TodoList集合。TodeView操作Todo。

在TodeView中会给Todo模型绑定change事件,也就是说,只要Todo模型改变了,相对应的视图就会执行render,把改变的model呈现出来。

在AppView中会给TodoList集合绑定all事件,只要集合TodoList有变化,就会调用AppView视图中的render方法,把改变的TodoList呈现出来。

从Backbone的例子中可以看出,MVC中的V不仅仅只是显示数据,还包括绑定事件,调用模型的方法等等。

这里面,模型只要有变化,视图就会改变,因为视图给模型绑定了change方法,这样就做到了单向绑定。但是视图变化的话,需要视图手动调用模型的save方法,改变模型。

接下来,我们来看看使用MVVM实现的一个例子:Angular实现的

<html ng-app=‘TestFormModule‘>  //添加ng-app指令到<html>标签中,这个指令会告诉Angular处理整个HTML页面,也就是说ng-app指令标记了AngularJS脚本的作用域,开发者也可以在局部使用ng-app指令,如<div ng-app>,则AngularJS脚本仅在该<div>中运行。
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <script src="../angular-1.0.3/angular.min.js"></script>   //引入angular库
    </head>
    <body>
        <form name="myForm" ng-submit="save()" ng-controller="TestFormController">   //ng-controller,一个指令,用于指定当前的模版对应的Controller为TestFormModule。ng-submit,一个指令,form表提交时需要做的事情,这里是调用save方法。
              <input name="userName" type="text" ng-model="user.userName" required/>  //ng-model,一个指令,用于指定input的值对应modeluser的userName变量。也就是说把此元素input绑定到一个叫user.userName的模型变量中
              <input name="password" type="password" ng-model="user.password" required/>  //把此元素input绑定到一个叫user.password的模型变量中
              <input type="submit" ng-disabled="myForm.$invalid"/>
        </form>
    </body>
    <script>
      var appModule = angular.module(‘TestFormModule‘, []);   //定义TestFormModule模块
      appModule.controller("TestFormController",function($scope){   //这个控制器的作用域$scope对所有<form ng-controller="TestFormModule">标签内的数据绑定有效。一个作用域可以视作视图、模型和控制器协同工作的粘接器。AngularJS使用作用域,这可以帮助模型和视图分离,但是他们两者却是同步的!任何对于模型的更改都会即时反映在视图上;任何在视图上的更改都会被立刻体现在模型中。
           $scope.user={      //初始化模型user
              userName:‘chaojidan‘,
              password:‘‘
           };
           $scope.save=function(){   //声明sava方法
              alert("保存数据!");
           }
      });

    </script>
</html>

上面这个例子的效果就是:有一个form表,form表中有两个input输入框和一个input按钮,第一个输入框的内容是chaojidan,第二个输入框的内容为"",input按钮是提交按钮。

当两个输入框中都有内容时,按钮才可点,当你点击按钮,页面上才会"弹出保存数据!",如果有一个没有内容,按钮是处于disable状态的,提交不了的。

在input输入框的元素上,有一个ng-model属性,这个属性的值对应$scope.user对象中的userName和password,我们可以暂且称$scope.user为model。也就是说,页面上的input输入框元素(我们称之为view),与model进行了绑定。view变化,model就会变化,model变化,view也会自动变化。这就是双向绑定。

同时,input按钮会绑定form表的验证事件,如果form表中的input输入框全部验证通过,就会把input按钮变为可点击,可提交。如果有一个验证不通过,就会让元素input按钮,变成不可点击,不可提交。

angular封装了所有的事件绑定,这里的view层,就是页面的元素。通过给元素上面添加专有的属性,来控制此元素。上面的这个例子,做到了View和Model的完全分离。

最后,大家应该还记得在使用backbone开发时,当model有变化时,就会把整个model的数据拼接到模板中(如果仅仅只改变了model的一个字段,而要重新组装整个模板,是不是觉得有点浪费),然后显示出来,但是在angular开发中,当model的userName有变化时,只需改变与模型user的userName字段绑定的input输入框,而不用重新把model的数据拼接到模板中显示出来。

但是,我们需要按照angular规定的语法,来写html和js,甚是不自由。

接下来的课程,将介绍如何使用angular.js来进行开发。

加油!

时间: 2024-07-29 00:54:19

第四十六课:MVC和MVVM的开发区别的相关文章

NeHe OpenGL教程 第四十六课:全屏反走样

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第四十六课:全屏反走样 全屏反走样 当今显卡的强大功能,你几乎什么都不用做,只需要在创建窗口的时候该一个数据.看看吧,驱动程序为你做完了一切. 在图形的绘制中,直线的走样是非常影响美观的,我们可以使用反走样解决这个问题.在众多的解决

JAVA学习第四十六课 — 其他对象API(二)Date类 &amp; Calendar类(重点掌握)

Date类(重点) 开发时,会时常遇见时间显示的情况,所以必须熟练Date的应用 <span style="font-family:KaiTi_GB2312;font-size:18px;"><strong>import java.util.*; public class Main { public static void main(String[] args){ long l = System.currentTimeMillis();//14140798929

第四十六课、继承中的构造与析构

一.子类对象的构造方法 1.子类中可以定义构造函数 2.子类构造函数:必须对继承来的成员进行初始化 (1).直接通过初始化列表或者赋值方式进行初始化(但可能继承来的是private成员) (2).调用父类构造函数进行初始化 A.默认调用:适用于无参构造函数和使用默认参数的构造函数 B.显示调用:通过初始化列表进行调用(适用于所有父类的构造函数) #include<iostream> #include<string> using namespace std; class Parent

python第四十六课——函数重写

3.函数重写(override) 前提:必须有继承性 原因: 父类中的功能(函数),子类需要用,但是父类中函数的函数体内容和我现在要执行的逻辑还不相符 那么可以将函数名保留(功能还是此功能),但是将函数体重构: 注意: 子类重写父类的函数,除了函数体以外的部分,直接复制父类的即可 演示函数重写的使用以及格式: class Fu: def test(self): print('九阳神功...') class Zi(Fu): def test(self): print('九阳神功...') supe

NeHe OpenGL教程 第三十六课:从渲染到纹理

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十六课:从渲染到纹理 放射模糊和渲染到纹理: 如何实现放射状的滤镜效果呢,看上去很难,其实很简单.把渲染得图像作为纹理提取出来,在利用OpenGL本身自带的纹理过滤,就能实现这种效果,不信,你试试. 嗨,我是Dario Corn

第十六课 数组的引入 【项目1-5】

第十六课 数组的引入 项目一 [数组大折腾] (1)创建一个有20个元素的整型数组,通过初始化,为数组中的前10个元素赋初值,然后通过键盘输入后10个元素的值,从前往后(从第0个到第19个)输出数组中元素的值,每5个元素换一行. [cpp] view plain copy print? int main( ) { int a[20]={...};  //初始化前10个元素 //键盘输入后10个元素的值 //由前往后输出数组中所有元素的值 printf("由前往后,数组中的值是:\n")

OpenGL教程翻译 第十六课 基本的纹理贴图

OpenGL教程翻译 第十六课 基本的纹理贴图 原文地址:http://ogldev.atspace.co.uk/(源码请从原文主页下载) Background 纹理贴图就是将任意一种类型的图片应用到3D模型的一个或多个面.图片(也可以称之为纹理)内容可以是任何东西,但是他们一般都是一些比如砖,叶子,地面等的图案,纹理贴图增加了场景的真实性.例如,对比下面的两幅图片. 为了进行纹理贴图,你需要进行三个步骤:将图片加载到OpenGl中,定义模型顶点的纹理坐标(以对其进行贴图),用纹理坐标对图片进行

程序员的奋斗史(四十六)——大学断代史(十)——给学弟学妹们的忠告——终结篇

文/温国兵 「写在前面」 大学断代史终于要完结了,就像一条再长的路总有终点一样.该系列文章前前后后写了一两个月,也该收尾了,至于收尾的文章,想了想,决定写写自己对学弟学妹的忠告.本篇文章以话题的形式呈现. 「关于专业」 我相信大多数的读者在高考填志愿都不知道软件工程或者计算机专业是做啥的,稀里糊涂就踏上了这条IT不归路.身处小乡村,消息相对闭塞,能使用电脑都是奢侈的事情,这就是当初我高考后的境况,相信现在有很大的改变.如果你对IT行业抱有一番热情,恭喜你,选对了好专业,好好学,今后的路错不了.如

2018-08-24 第三十六课

第三十六课 非关系统型数据库-mangodb 目录 二十四 mongodb介绍 二十五 mongodb安装 二十六 连接mongodb 二十七 mongodb用户管理 二十八 mongodb创建集合.数据管理 二十九 php的mongodb扩展 三十 php的mongo扩展 三十一 mongodb副本集介绍 三十二 mongodb副本集搭建 三十三 mongodb副本集测试 三十四 mongodb分片介绍 三十五 mongodb分片搭建 三十六 mongodb分片测试 三十七 mongodb备份