最近一直在学习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. 纯手工打造简单分布式爬虫(Python)

    前言 这次分享的文章是我<Python爬虫开发与项目实战>基础篇 第七章的内容,关于如何手工打造简单分布式爬虫 (如果大家对这本书感兴趣的话,可以看一下 试读样章),下面是文章的具体内容. ...

  2. Java程序设计环境概述

    本文主要Java程序设计环境的要点,以及相关注意事项. 一.安装Java开发包 Oracle公司为Linux.Mac OS X.Solaris和Windows提供了Java开发工具包(JDK)的最新. ...

  3. IntelliJ IDEA的激活和汉化

    1.下载 IntelliJ IDEA 下载地址 Community 社区版,免费使用,下载后发现没有JAVA EE,推荐下载 Ultimate Ultimate 需要注册码. 2.激活 我下载的是20 ...

  4. POJ 1845-Sumdiv 题解(数论,约数和公式,逆元,高中数学)

    题目描述 给定A,B,求A^B的所有因数的和,再MOD 9901 输入 一行两个整数 A 和 B. 输出 一行,一个整数 样例输入 2 3 样例输出 15 提示 对于100%的数据满足:0 <= ...

  5. module.exports,exports,export和export default,import与require区别与联系【原创】

    还在为module.exports.exports.export和export default,import和require区别与联系发愁吗,这一篇基本就够了! 一.首先搞清楚一个基本问题: modu ...

  6. markdown基础

    介绍: markdown是一种可以使用普通文本编译器编写的标记语言,通过简单的标记语法,它可以使普通文本具有一定的格式.说的简单一点,markdown其实就是一种简单的文本,与普通的文本文件(txt文 ...

  7. Vuejs技术栈从CLI到打包上线实战全解析

    前言 本文是自己vue项目实践中的一些总结,针对Vue2及相关技术栈,实践中版本为2.3.3. 开发前须知 vue-cli 在开发前,我们要至少通读一遍vue官方文档和API(看官方文档是最重要的,胜 ...

  8. 让gdb能打印C++中的容器类型

    由于原生的gdb对vector,map等容器的支持不太好,所以找到了一个工具,将这个工具集成到gdb中,就可以实现map,vector等容器的内容的打印操作. 1.用vim将下方的代码拷贝到一个新的文 ...

  9. 【LeetCode】122. Best Time to Buy and Sell Stock II

    题目: Say you have an array for which the ith element is the price of a given stock on day i. Design a ...

  10. 简单说下Kanzi Studio

    一.Project 窗口 在Project窗口下可以创建界面节点,包含有Screen和Prefabs 二.Properties窗口 包含有节点的相关属性,不同类型的节点,属性不同.主要通过改变节点的属 ...