主题:如何在应用中使用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
需要的相关知识:
在解决了向服务器端的数据抓取后,就需要把这些抓取的数据添加进我们创建的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;
}
}
怎么使用这两个对象呢?示例代码如下: