MVC:模型Model与数据(未完成)

2017-07-15  本文已影响38人  BryantHe

主题:如何在应用中使用MVC模型,包括加载和操作远程数据

包括以下分支:

1.为什么构建ORM类库时,MVC和命名空间很重要
2.如何使用ORM类库来管理模型数据
3.如何使用JSONP和跨域Ajax来远程加载数据
4.如何通过使用HTML5本地存储和将本地存储提交至RESTful服务器,来实现模型数据持久化


1.为什么构建ORM类库时,MVC和命名空间很重要

把数据管理工作迁到前台的一个好处:

那么怎么在前端,也就是客户端架构数据存储的模型呢?

引入MVC,那么数据管理则归入模型(M)。模型应当从视图和控制器中解耦出来。与数据操作和行为相关的逻辑都应当放入模型中,通过命名空间进行管理(模块化,引入需要的,再暴露出API)。

但这里的命名空间主要指通过对象/类来封装模型,避免模型暴露在全局,导致可能的作用域污染。

用对象封装后,数据通常用数组来存储(数组内又可以嵌套对象),或者用Ajax等调用远程服务器数据的函数来获取数据(然后也可存在前面的数组,或者看具体场景)。

为了让模型更加抽象,可复用,需要把模型写成类。在JS里则是用原型继承

e.g:

var User = function(atts) {
    this.attributes = atts || {};
};

User.prototype.destory = function () {
    / ...  / 
};

(ps:这是一种不被推荐的类写法,在这只用作例子说明)

对于那些不需要复用,只在User内部使用的函数或变量就可以不作为原型继承,直接写在User里:

User.fetchRemote = function () {
    / .... /  
};

这个方法就只是User拥有,而不会被实例继承。

小结:通过命名空间和MVC的分层,能更好的对数据进行管理,避免出现混乱的情况。


2.如何使用ORM类库来管理模型数据

对象关系映射(ORM, Object-relational mapper)是除了JS外的编程语言中常见的一种数据结构。

由于现在前端承担了更多的任务,所以有了管理数据的需求,因此对象关系映射对于JS来说也成了一种非常有用的技术,它可以用来做数据管理和用做模型。

比如用ORM将模型和远程服务绑在一起,然后模型实例的改变都会发起Ajax请求到服务器端(或get数据或put数据)。或者将模型实例和HTML元素绑定,模型实例的数据改变会在界面中反映出来。

如何自定义一个ORM

这里使用Object.create()。传入一个原型对象作为参数,会返回一个继承了这个原型对象的新对象。

JS的原型继承机制十分强大,用这个继承机制写的Model十分灵活(因为在模型创建好后,还可以动态扩展属性方法,不仅对模型本身扩展,对模型的实例也可以)。

现在创建Model对象:

var Model = { 
  inherited: function () {}, // 存放被继承的模型
  created: function () {}, 

  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  }
};

create()函数返回新对象,这个对象继承自Model对象,我们用这个返回的新对象来赋值给新模型。

init()函数返回新对象,这个对象继承自Model.prototype,即继承自Model对象的新对象的实例。这么说有点绕,展示示例:

var Asset = Model.create();
var User = Model.create();  

这是用create()函数创建的新模型。

var user = User.init();

这是用init()函数返回的一个实例,继承了Model.prototype的所有属性和方法。相反,这也意味着可以通过给Model.prototype添加属性和方法,来动态的给所有实例添加这些属性和方法。

当然,我们不直接这么用,而是用extend()和include()这两个函数封装添加模型的属性方法添加实例的属性方法这两个功能:

var Model = {
  inherited: function () {},
  created: function () {},

  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  },

  // 添加继承的模型的属性
  extend: function (obj) {
    var extended = obj.extended;
    jQuery.extend(this, obj );
    if (extended) {
      extended(this);
    }
  },

  // 添加继承的模型的实例的属性
  include: function (obj) {
    var included = o.included;
    jQuery.extend(this.prototype, obj);
    if (included) {
      included(this);
    }
  } 
};

extend()给Asset和User模型添加属性:

Model.extend({
    find: function () {}
};

Asset.find(); 
// Asset模型拥有了find,前提是先给Model添加find
// 不是基于原型的这种不能动态添加属性方法

include()方法给user实例动态添加属性,即不论创建时间的先后:

Model.include({
  init: function (atts) {
    if (atts) {
      this.load(atts);
    }
  },

  load: function (attributes) {
    for (var name in attributes) {
      this[name] = attributes[name];
    }
  }
});

// 实例便拥有了init()和load()方法
var asset = Asset.init( {name: "foo.png"} );

下面则是进一步完善的模型:

var Model = {
  created: function () {},
  
  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  },

  // 添加继承的模型的属性
  extend: function (obj) {
    var extended = obj.extended;
    jQuery.extend(this, obj );
    if (extended) {
      extended(this);
    }
  },

  // 添加继承的模型的实例的属性
  include: function (obj) {
    var included = obj.included;
    jQuery.extend(this.prototype, obj);
    if (included) {
      included(this);
    }
  } 
};


Model.extend({
  // 一个继承模型的实例想要持久化记录数据
  created: function () {
    this.records = {};
  },
  
  find: function () {
    var record = this.records[id];
    if (!record) {
      throw("Unknown record");
    }
    return record.dup();
  }  
});

// 模型继承时,继承的模型可以传入自定义的属性
Model.include({
  init: function (atts) {
    if (atts) {
      this.load(atts);
    }
  },

  load: function (attributes) {
    for (var name in attributes) {
      this[name] = attributes[name];
    }
  }
});


Model.include({
  // 初始化为true,因为本就是新增加的一条记录
  newRecord: true, 

  create: function () {
    // 为每个实例加上ID
    if (!this.id) {
      this.id = Math.guid();
    }
    // 创建了一天,所以要把newRecord这个状态判断换位false
    this.newRecord = false;
    // 存入records
    this.parent.records[this.id] = this.dup();
  },

  destroy: function () {
    delete this.parent.records[this.id];
  },

  update: function () {
    this.parent.records[this.id] = this.dup();
  },

  dup: function () {
    return jQuery.extend(true, {}, this);
  }

  // 这个方法作用是每次给实例添加好数据后,都使用save来保存,
  // 并且有个判断,如果是新记录则创建,已经存在则更新
  save: function () {
    this.newRecord ? this.create() : this.update();
  }
});


// 生成随机ID
Math.guid = function(){
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
    function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    }).toUpperCase();      
};


3.如何使用JSONP和跨域Ajax来远程加载数据

通过远程服务器获取数据,然后写入ORM

需要的相关知识:

Ajax

Ajax的新规范Fetch API

jQuery的Ajax接口

JSONP

跨域通信CORS

在解决了向服务器端的数据抓取后,就需要把这些抓取的数据添加进我们创建的ORM。

具体来说,在用Ajax或者JSONP抓取数据后,我们在其回调函数里加入我们预定义的处理函数,这个处理函数主要就是把json格式的数据通过遍历处理,再创建实例,更新进records对象中。

具体的代码为(会合并进前面的完整Model里):

Model.extend({
  populate: function( values ) {
    // 重置model和records
    this.records = {};

    for (var i= 0; i < values.length; i++ ) {
      var record = this.created(values[i]);
      record.newRecord = false;
      this.records[record.id] = record;
    }    
  }
});

这个方法的用法示例:

// 当我们用jQuery的JSONP接口获得了数据后...
jQuery.getJSON("/assets", function(result) {
    Asset.populate(result);
});

4.如何通过使用HTML5本地存储和将本地存储提交至RESTful服务器,来实现模型数据持久化

通过本地缓存获取数据,然后写入ORM

这个就先要在相关位置设置“存储缓存”,然后才在需要的方法调用缓存的数据。

因此先记录一下Web Storage API:

这个API的作用是,使得网页可以在浏览器端储存数据。它分成两类:sessionStorage和localStorage。

他们的不同之处:
  • sessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;
  • localStorage保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。
他们的相同之处:
  • 保存期限的长短不同,这两个对象的属性和方法完全一样。

他们很像cookie机制的强化版,能够动用大得多的存储空间。另外,与Cookie一样,它们也受同域限制。

怎么判断浏览器是否支持这两个对象?代码如下:

function checkStorageSupport() {
 
  // sessionStorage
  if (window.sessionStorage) {
    return true;
  } else {
    return false;
  }
   
  // localStorage
  if (window.localStorage) {
    return true;
  } else {
    return false;
  }
}

怎么使用这两个对象呢?示例代码如下:



上一篇下一篇

猜你喜欢

热点阅读