读书笔记_MVC__关于通过js构建ORM,实现Model层
最近一直在学习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层的更多相关文章
- [读书笔记]了不起的node.js+实践(一)
环境的变化带来了技术大跃进,机遇和挑战同时到来.基于我js也没有学,只好赶鸭子上架一起学了.(>﹏<) 1.先读读书 一开始就不知死活地看<深入浅出node.js>,弄得团团转 ...
- [读书笔记]了不起的node.js(三)
这周的nodejs学习内容为几个依赖包的使用,把书上的例子都敲了一遍.这篇就以例程为线索,复习一下一周的工作. 1.connect 这个例程主要是使用connect依赖包,connect提供一个中间件 ...
- 《Java并发编程实战》读书笔记-第5章 基础构建模块
同步容器类 同步容器类实现线程安全的方式:将所有状态封装起来,对每个公有方法使用同步,使得每一次只有一个线程可以访问.同步容器类包含:Vector.Hashtable.Collections.sync ...
- [读书笔记]了不起的node.js(四)
这周的学习主要是nodejs的数据库交互上,并使用jade模板一起做了一个用户验证的网站.主要是遇到了一下几个问题. 1.mongodb版本过低 npm ERR! Not compatible wit ...
- [读书笔记]了不起的node.js(二)
这周做项目做得比较散(应该说一直都是这样),总结就依据不同情境双开吧-这篇记录的是关于node的学习总结,而下一篇是做项目学到的web前端的知识. 1.HTTP篇 node的HTTP模块在第一篇时接触 ...
- .net架构设计读书笔记--第二章 第7节 神化般的业务层
一.编排业务逻辑的模式1. 事务脚本模式TS(The Transaction Script pattern ) TS模式概述 TS 鼓励你跳过任何的面向对象的设计,你直接到所需的用户操作的业务 ...
- 《TCP/IP详解卷1:协议》第6章 ICMP:Internet控制报文协议-读书笔记
章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...
- SQL 横转竖 、竖专横 (转载) 使用Dapper.Contrib 开发.net core程序,兼容多种数据库 C# 读取PDF多级书签 Json.net日期格式化设置 ASPNET 下载共享文件 ASPNET 文件批量下载 递归,循环,尾递归 利用IDisposable接口构建包含非托管资源对象 《.NET 进阶指南》读书笔记2------定义不可改变类型
SQL 横转竖 .竖专横 (转载) 普通行列转换 问题:假设有张学生成绩表(tb)如下: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 ...
- Node.js高级编程读书笔记Outline
Motivation 世俗一把,看看前端的JavaScript究竟能做什么. 顺便检验一下自己的学习能力. Audience 想看偏后台的Java程序员关于前端JavaScript的认识的职业前端工程 ...
随机推荐
- cp的用法
1.cp的功能 拷贝一个或多个文件(或目录)到目的地 2.例子 1)一次拷贝多个源文件到目的地#cp /mnt/hgfs/DOC/{1,2,3,4,5}.txt /root/ldj 2)只拷贝链接文件 ...
- Python 任务队列 Celery
一. celery 简介 Celery 是一个专注于实时处理和任务调度的分布式任务队列, 同时提供操作和维护分布式系统所需的工具.. 所谓任务就是消息, 消息中的有效载荷中包含要执行任务需要的全部数据 ...
- 深入浅出HTTP协议
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议.所有的WWW文件都必须遵守这个标准.设计HTTP最初的目的是为了提供一种发布和接 ...
- grid栅格布局
前面的话 Grid布局方式借鉴了平面装帧设计中的格线系统,将格线运用在屏幕上,而不再是单一的静态页面,可以称之为真正的栅格.本文将详细介绍grid布局 引入 对于Web开发者来说,网页布局一直是个比较 ...
- Install Composer on CentOS
First you have to go to the /tmp directory cd /tmp Download the composer.phar file curl -sS https:// ...
- mysql安装不上 failed to install the service
先前安装的没有卸载干净必须删除相应的注册表方法如下:1)“运行”中敲入“Regedit”进入注册表编辑2)HKEY_LOCAL_MACHINE->SYSTEM->ControlSet001 ...
- 大话Session
[原创]转载请保留出处:shoru.cnblogs.com 晋哥哥的私房钱 引言 在web开发中,session是个非常重要的概念.在许多动态网站的开发者看来,session就是一个变量,而且其表现像 ...
- 【WPF】在新线程上打开窗口
当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环.直到消息循环结束,应用程序就随即退出.那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗 ...
- JavaScript学习笔记(一)——初识js
这个周,开始了JavaScript的学习路程.虽然从高中开始就接触了网页设计的知识,大学里的学习也算回顾了Html和Css的知识,实习期间在牛盾科技也是做的网站建设,不过通过前段时间找工作才发现自己了 ...
- biz-NewsService
package com.pb.news.service; import java.util.List; import com.pb.news.entity.News; public interface ...