最近一直在学习MVC构建富应用的WEB程序,自己一直对MVC的设计模式理解的不是十分透彻,终于在研读了github上Spine的源码之后,对构建Model层有了一点自己的理解。

本文仅为个人理解,如有问题,欢迎指正。

对象关系映射(Ojbect-relational mapper,简称 ORM)是在除 JavaScript 以外的编程语言中常见的一种数据结构。然而在 JavaScript 应用中,对象关系映射也是一种非常有用的技术,它可以用来做数据管理及用作模型。比如使用 ORM 你可以将模型和远程服务捆绑在一起,任何模型实例的改变都会在后台发起一个 Ajax 请求到服务器端。或者你可以将模型实例和 HTML 元素绑定在一起,任何对实例的更改都会在界面中反映出来。本质上讲,ORM 是一个包装了一些数据的对象层。以往 ORM 常用于抽象 SQL 数据库,但在这里 ORM 只是用于抽象 JavaScript 数据类型。这个额外的层有一个好处,我们可以通过给它添加自定义的函数和属性来增强基础数据的功能。比如添加数据的合法性验证、监听、数据持久化及服务器端的回调处理等,这样会增加代码的重用率。

在我个人理解,其实就是在程序中创建一个虚拟数据库,与我们现实数据库通过映射关系对应。

$1. Model的建立

  Model对象是为创建新模型与实例存在的。此处采用了javascript原型继承的方式。因为我个人大学一直是学习嵌入式开发,对面向对象的编程思想基本从未涉及。javascript原型继承的概念在此处不再赘述。以后上传javascript面向对象编程的读书笔记时会提到这点。

var Model = (function(){
if(typeof Object.create !== "function"){
Object.create = function(o){
function F(){};
F.prototype = o;
return new F();
}
}
return {
inherited: function(){},
created: function(){},
records: {},
create: function(){
var obj = Object.create(this);
obj.parent = this;
obj.prototype = obj.fn = Object.create(this.prototype);
obj.created();
this.inherited(obj);
return obj;
},
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);
}
}
})();
Model.prototype = {
init: function(attrs){
if(attrs){
for(var name in attrs){
this[name] = attrs[name];
}
}
}
}

通过代码我们可以发现,我们构建了Model对象,我们使用的是基于原型的继承,没有用到构造函数和new关键字。

Object.create() 只有一个参数即原型对象,它返回一个新对象,这个新对象的原型就是传入的参数。换句话说,传入一个对象,返回一个继承了这个对象的新对象。当然我们也同时模拟出了这个函数。

这段代码其实在理解了Object.create()的基础上还是比较清晰地。Model.create()函数返回一个新对象,他继承自Model对象,我们使用他创建模型。Model.init()函数返回一个新对象,继承自Model.prototype。我们可以看作是Model对象的一个实例。

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

$2. 给Model添加属性

我们当然希望在model建立之初就可以对他的属性方法定义完全,但一般再优秀的架构师也不可能直接设计出完美的系统。所以当我们需要扩展我们的model时,我们肯定不希望我们还是在使用

model.check = function(){}, model.prototype.push = function(){}, 为了代码的优雅和体现函数复用,我们在上文extend与include两种方法。javascript中当一个对象在自己身上找不到某种特定方法时默认向上寻找,这个向上指原型指向,所以我们需要区分属性到底挂载在哪里,我们肯定不希望我们调用方法时出现undefined。extend用来扩展model的属性,include用来扩展model.prototype的属性。

所以当我们需要给某个实例来挂载方法时,我们肯定也希望其他实例也可以使用,我们需要使用include。代码中我们定义了parent即为他的父级对象。其实更好的方法还是去设定一下parent.name来指向具体的指向,更直白与简洁。

// 添加对象属性
Model.extend({
find: function(){}
});
// 添加实例属性
Model.include({
init: function(atts) { /* ... */ },
load: function(attributes){ /* ... */ }
});

$.3 持久化记录

我们需要一种保持记录持久化的方法,即将引用保存至新创建的实例中以便任何时候都能访问它。

我们通过在 Model 中使用 records 对象来实现。当我们保存一个实例的时候,就将它添加进这个对象中;当删除实例时,和将它从对象中删除。更新一个已存在的实例只需更新对象引用即可。

            Model.include({
newRecord: true,
create: function(){
this.newRecord = false;
this.parent.records[this.id] = this;
},
destroy: function(){
delete this.parent.records[this.id];
},
update: function(){
this.parent.records[this.id] = this;
},
save: function(){
this.newRecord ? this.create() : this.update();
}
})
Model.extend({
find: function(id){
return this.records[id] || console.log("Unknow record");
}
})

我们定义了一个保存函数save(),这样就不用每次都检查这个实例是否已经保存过或是否需要新创建实例了。至此我们的基本数据模型就实现了。

var Asset = Model.create();
var asset = Asset.init({name: "tom", age: 20});
console.log(asset);
asset.init({name: "mary",age: 30});
console.log(asset);
asset.save();
console.log(asset);
asset.destroy_on_prop();
console.log(asset);

此时可以断点添加监听调试,查看每一个函数执行之后asset的变化。

$.4 添加id查询与寻址引用

我们肯定希望在通过一个唯一标识在我们的数据模型中寻找具体的实例。此时,每次我们保存一条记录都必须手动指定一个 ID。这实在是糟透,但幸运的是,我们可以加入自动化处理。首先,我们需要一个方法来自动生成 ID,可以使用全局统一标识(Globally Unique Identifier,简称 GUID)生成器来做这一步虽然 JavaScript 中内置的 Math.random() 方法尽管产生的是伪随机数,但也足够用了。

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();
};
Model.extend({
create: function(){
if ( !this.id ) this.id = Math.guid();
this.newRecord = false;
this.parent.records[this.id] = this;
}
});

我们将guid()方法重新集成到数据模型中,并对create()进行了重写。这样任何新创建的记录都包含一个随机的 GUID 作为它们的 ID。

但是此时存在关于引用有一个bug,当保存或通过 find() 查找记录时,所返回的实例并没有复制一份,因此对任何属性的修改都会影响原始资源。这的确是一个问题,因为我们只想当调用 update() 方法的时候才会修改资源。我们需要修复这个问题,在执行 find() 操作的时候新创建一个对象,同样在创建或更新记录的时候需要复制对象。同理,model.records此时是被所有模型共享的对象,这样在合并所有记录时会出现问题。解决办法是在创建新模型时设置一个新的records对象。Model.create()是创建新对象的回调,因此我们可以设置任意描述这个模型的对象。

Asset.extend({
find: function(id){
var record = this.records[id];
if ( !record ) throw("Unknown record");
return record.dup();
}
});
Asset.include({
create: function(){
this.newRecord = false;
this.parent.records[this.id] = this.dup();
},
update: function(){
this.parent.records[this.id] = this.dup();
},
dup: function(){
return jQuery.extend(true, {}, this);
}
});
//reset records
Model.extend({
created: function(){
this.records = {};
}
});

最后,我们的初始的数据模型已经构建完毕。相信有人注意到我们extend()不止是用来扩展属性方法,同样可以用于重写方法。这对于我们之后的调试来说是非常方便的。技术简陋,自己也是于MVC设计中在一点点寻找答案。如果有问题还希望各位大神指正。

读书笔记_MVC__关于通过js构建ORM,实现Model层的更多相关文章

  1. [读书笔记]了不起的node.js+实践(一)

    环境的变化带来了技术大跃进,机遇和挑战同时到来.基于我js也没有学,只好赶鸭子上架一起学了.(>﹏<) 1.先读读书 一开始就不知死活地看<深入浅出node.js>,弄得团团转 ...

  2. [读书笔记]了不起的node.js(三)

    这周的nodejs学习内容为几个依赖包的使用,把书上的例子都敲了一遍.这篇就以例程为线索,复习一下一周的工作. 1.connect 这个例程主要是使用connect依赖包,connect提供一个中间件 ...

  3. 《Java并发编程实战》读书笔记-第5章 基础构建模块

    同步容器类 同步容器类实现线程安全的方式:将所有状态封装起来,对每个公有方法使用同步,使得每一次只有一个线程可以访问.同步容器类包含:Vector.Hashtable.Collections.sync ...

  4. [读书笔记]了不起的node.js(四)

    这周的学习主要是nodejs的数据库交互上,并使用jade模板一起做了一个用户验证的网站.主要是遇到了一下几个问题. 1.mongodb版本过低 npm ERR! Not compatible wit ...

  5. [读书笔记]了不起的node.js(二)

    这周做项目做得比较散(应该说一直都是这样),总结就依据不同情境双开吧-这篇记录的是关于node的学习总结,而下一篇是做项目学到的web前端的知识. 1.HTTP篇 node的HTTP模块在第一篇时接触 ...

  6. .net架构设计读书笔记--第二章 第7节 神化般的业务层

    一.编排业务逻辑的模式1. 事务脚本模式TS(The Transaction Script pattern ) TS模式概述     TS 鼓励你跳过任何的面向对象的设计,你直接到所需的用户操作的业务 ...

  7. 《TCP/IP详解卷1:协议》第6章 ICMP:Internet控制报文协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  8. SQL 横转竖 、竖专横 (转载) 使用Dapper.Contrib 开发.net core程序,兼容多种数据库 C# 读取PDF多级书签 Json.net日期格式化设置 ASPNET 下载共享文件 ASPNET 文件批量下载 递归,循环,尾递归 利用IDisposable接口构建包含非托管资源对象 《.NET 进阶指南》读书笔记2------定义不可改变类型

    SQL 横转竖 .竖专横 (转载)   普通行列转换 问题:假设有张学生成绩表(tb)如下: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 ...

  9. Node.js高级编程读书笔记Outline

    Motivation 世俗一把,看看前端的JavaScript究竟能做什么. 顺便检验一下自己的学习能力. Audience 想看偏后台的Java程序员关于前端JavaScript的认识的职业前端工程 ...

随机推荐

  1. Mac OS中使用VScode配置C语言开发环境

    个人博客 chinazt.cc 闲话少叙,直奔主题 下载VSCode https://code.visualstudio.com/download 安装C/C++插件 需要两个插件: 1. cppto ...

  2. jdbc的配置及jdbc连接常用数据库(mysql、sqlserver、Oracle)

    1.连接SQL Server数据库 import java.sql.*; publicclassMain{publicstaticvoid main(String[] args){String dri ...

  3. Ajax请求,跨域小坑

    今天在上班的时候,被坐在旁边项目经理叫过去问了一个Ajax请求跨域的问题,一开始没理解清楚也还有对这个没有理解的透,后面被打击的要死. 当时的需求是需要测试一个已发布的api接口,需要在本地写测试程序 ...

  4. CNN压缩:为反向传播添加mask(caffe代码修改)

    神经网络压缩的研究近三年十分热门,笔者查阅到相关的两篇博客,博主们非常奉献的提供了源代码,但是发发现在使用gpu训练添加mask的网络上,稍微有些不顺,特此再进行详细说明. 此文是在 基于Caffe的 ...

  5. 读Zepto源码之属性操作

    这篇依然是跟 dom 相关的方法,侧重点是操作属性的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1.2. ...

  6. ReactiveCocoa源码解析(四) Signal中的静态属性静态方法以及面向协议扩展

    上篇博客我们聊了Signal的几种状态.Signal与Observer的关联方式以及Signal是如何向关联的Observer发送事件的.本篇博客继续上篇博客的内容,来聊一下Signal类中静态的ne ...

  7. 12.exception对象

    excepton对象是一个异常对象,当一个页面在运行过程中发生了异常,就产生了这个对象,如果一个jsp页面要应用此对象,就必须把isErrorPage设置为true,否则无法编译.它实际上是java. ...

  8. 用php+mysql+ajax实现淘宝客服或阿里旺旺聊天功能 之 前台页面

    首先来看一下我已经实现的效果图: 消费者页面:(本篇随笔) (1)会显示店主的头像 (2)当前用户发送信息显示在右侧,接受的信息,显示在左侧 店主或客服页面:(下一篇随笔) (1)在左侧有一个列表 , ...

  9. H5本地储存Web Storage

    一.本地存储由来的背景 由于HTML4时代Cookie的大小.格式.存储数据格式等限制,网站应用如果想在浏览器端存储用户的部分信息,那么只能借助于Cookie.但是Cookie的这些限制,也就导致了C ...

  10. VMware Mac OS中无法找到适应的分辨率的解决办法

    使用VMware安装的Mac OS中,有时在显示器的分辨率中的选择项里面,没有对应显示的分辨率可供选择的时候(无法自适应),可以在虚拟机设置里,显示器中修改强制分辨率.修改过后重启虚拟机,就可以有对应 ...