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

主题:如何在应用中使用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;
  }
}

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



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容