您的位置:首页 > Web前端 > JavaScript

web前端之MVC的JavaScript Web富应用开发三:模型和数据

2016-12-26 19:21 691 查看

web前端之MVC的JavaScript Web富应用开发三:模型和数据

将应用重心从后台迁往前台的一个挑战是数据管理。 传统方式是通过页面请求从数据库获取数据, 用户和页面中的结果进行直接交互。 然而在复杂的 JavaScript 应用中做数据管理是非常困难的。 前端并没有请求 / 响应模型, 也没办法访问服务器端的变量, 甚者,远程取回的数据只是临时的保存在客户端。

尽管这种( 从后台迁往前台的) 转变依然在激烈争论之中, 但这还是有一些好处的。 比如, 客户端数据存储速度非常快, 几乎是瞬间读取的, 因为数据是直接从内存中获得的。这会让你的应用接口变得与众不同, 所有交互操作都会瞬间得到响应, 这极大地提高了产品的用户体验。

MVC 和命名空间:

要确保应用中的视图、 状态和数据彼此清晰分离, 这样才能让架构更加整洁有序且更加健壮。 引入 MVC 模式, 数据管理则归入模型( MVC 中的 M)。 模型应当从视图和控制器中解耦出来。 与数据操作和行为相关的逻辑都应当放入模型中, 通过命名空间进行管理。

在 JavaScript 中, 我们通过给对象添加属性来管理一个命名空间, 这个命名空间可以是函数, 也可以是变量, 比如 :

var User={
records:[/*...*/]
};


User 的数组数据就在命名空间 User.records 中。 和 user 相关的函数也可以放入 User 模型的命名空间里。

将模型的属性保存至命名空间中的做法可以确保不会产生冲突, 这也是符合 MVC 原则的, 同时也能避免你的代码变成一堆函数和回调混杂在一起的大杂烩。

我们可以对命名空间做一点改进, 将那些在真实 user 对象上的和 user 实例相关的函数也都添加进去。 假设 user 记录包含一个 destory() 函数, 它是和具体的 user 相关的, 因此这个函数应当基于 User 实例进行调用 :

var user = new User;

user.destroy()

为了做到这一点, 我们应当将 User 写成一个类, 而不是一个简单对象 :

var User=function(atts){
this.attributes=atts || {};
};
User.prototype.destory=function(){
/*...*/
};


对于那些和具体的 user 不相关的函数和变量, 则可以直接定义在 User 对象中 :

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


构建对象关系映射( ORM):

对象关系映射是在除 JavaScript 以外的编程语言中常见的一种数据结构。 然而在 JavaScript 应用中, 对象关系映射也是一种非常有用的技术, 它可以用来做数据管理及用做模型。 比如使用 ORM 你可以将模型和远程服务捆绑在一起, 任何模型实例的改变都会在后台发起一个 Ajax 请求到服务器端。 或者你可以将模型实例和 HTML 元素绑定在一起, 任何对实例的更改都会在界面中反映出来。

本质上讲, ORM 是一个包装了一些数据的对象层。 以往 ORM 常用于抽象 SQL 数据库,但在这里 ORM 只是用于抽象 JavaScript 数据类型。 这个额外的层有一个好处, 我们可以通过给它添加自定义的函数和属性来增强基础数据的功能。 比如添加数据的合法性验证、 监听、 数据持久化及服务器端的回调处理等, 这样会增加代码的重用率。

原型继承:

这里使用 Object.create() 来构造我们的 ORM, 这和在前面提到的基于类的例子有一点不同。 这里使用基于原型( prototype-based) 的继承, 而没有用到构造函数和 new关键字。

Object.create() 只有一个参数即原型对象, 它返回一个新对象, 这个新对象的原型就是传入的参数。 换句话说, 传入一个对象, 返回一个继承了这个对象的新对象。

Object.create() 最近才添加进了 ECMAScript 第 5 版规范, 因此在有些浏览器中并未实现, 比如 IE。 但这并不是什么大问题, 如果需要的话, 我们可以很容易地模拟出这个函数 :

if (typeof Object.create !== "function"){
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}


现在来创建 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();

var user = User.init();

添加 ORM 属性:

现在如果给 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;
}
};
//添加对象属性
jQuery.extend(Model,{
find:function(){}
});
//添加实例属性
jQuery.extend(Model.prototype,{
init:function(atts){
if(atts){
this.load(atts);
}
}
,load:function(attributes){
for(var name in attributes){
this[name]=attributes[name];
}
}
});


jQuery.extend() 只是代替 for 循环手动复制属性的一种快捷方式, 这和我们在 load()函数中的做法差不多。 现在, 我们的对象和实例属性都传播到了单独的模型里 :

assertEqual( typeof Asset.find, “function” );

实际上我们会增加很多属性, 因此还需将 extend() 和 include() 添加至 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;
}
,extend:function(o){
var extended=o.extended;
jQuery.extend(this,o);
if(extended){
extended(this);
}
}
,include:function(o){
var included=o.included;
jQuery.extend(this.prototype,o);
if(included){
included(this);
}
}
};
//添加对象属性
Model.extend({
find:function(){}
});
// 添加实例属性
Model.include({
init:function(atts){
/*...*/
}
,load:function(attributes){
/*...*/
}
});


现在我们可以创建新的资源并设置一些属性 :

var asset = Asset.init({name: “foo.png”});

持久化记录:

我们需要一种保持记录持久化的方法, 即将引用保存至新创建的实例中以便任何时候都能访问它。 我们通过在 Model 中使用 records 对象来实现。 当我们保存一个实例的时候,就将它添加进这个对象中 ; 当删除实例时, 和将它从对象中删除 :

Model.records={};
Model.include({
newRecord:true
,create:function(){
this.newRecord=false;
this.parent.records[this.id]=this;
}
,destory:function(){
delete this.parent.records[this.id];
}
});


如何更新一个已存在的实例呢? 简单——只需更新对象引用即可 :

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


现在创建一个快捷函数来保存实例, 这样就不用每次都检查这个实例是否已经保存过或是否需要新创建实例了 :

// 将对象存入 hash 记录中, 保持一个引用指向它
Model.include({
save: function(){
this.newRecord ? this.create() : this.update();
}
});


如何实现一个用来根据 ID 查找资源的 find() 函数呢?

Model.extend({
// 通过 ID 查找, 找不到则抛出异常
find: function(id){
return this.records[id] || throw("Unknown record");
}
});


现在我们已经成功地创建了一个基本的 ORM, 我们来运行一下 :

var asset = Asset.init();
asset.name = "same, same";
asset.id = 1
asset.save();
var asset2 = Asset.init();
asset2.name = "but different";
asset2.id = 2;
asset2.save();
assertEqual( Asset.find(1).name, "same, same" );
asset2.destroy();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息