Javascript MVC 学习笔记(一) 模型和数据
写在前面
近期在看《MVC的Javascript富应用开发》一书。本来是抱着一口气读完的想法去看的。结果才看了一点就傻眼了:太多不懂的地方了。
仅仅好看一点查一点,一点一点往下看吧,进度虽慢但也一定要坚持看完。
本学习笔记是对书上所解说内容的理解和记录。
笔记里的代码大多会按书上摘录下来,由于《MVC的Javascript富应用开发》是结合了JQuery库。所以对于JQuery中不太懂的知识点也会附在代码后面,也算是一些额外的收获。
MVC概述
要学习MVC,首先得知道MVC是什么。MVC有三个字母,代表了它将应用划分为了三个部分:数据(模型Model)、展现层(视图View)和用户交互层(控制器Controller)。
对于採用MVC模式搭建的应用,一个事件的发生是这种过程:
- 用户和应用产生交互
- 控制器的事件处理器被触发
- 控制器从模型中请求数据,并将其交给视图
- 视图将数据呈现给用户
MVC的目的在于将M和V的实现代码分离,从而使同一个程序能够使用不同的表现形式。
介绍完MVC,就開始分别学习MVC三个组成部分了。
模型
模型用来存放应用的全部数据对象,它仅仅需包括数据以及直接和这些数据相关的逻辑。像其它什么事件监听、视图模版都应该隔离在模型之外。当控制器从server抓取数据或创建新的记录时,它就将数据包装成模型实例
比方你的应用里可能有非常多用户相关的数据:比方从server拿到的用户的name、id。在应用里面,又会对用户进行加入、改动、删除。那么用户就应该成为你的一个模型。
一个应用可能有多个模型(比方用户、商品),所以书中创建了一个Model对象,代表模型,对于不同的模型。将使用create方法创建,对于模型中详细的数据对象,使用init方法实例化。(听起来有点像类和对象),所以我们最后要实现的模型应该是以下的样子:
//创建Asset模型
var Asset = Model.create();
//实例化一个Asset的对象
var asset = Asset.init();
asset.name = "same, same";
asset.id = 1;
asset.save();
//实例化另一个Asset的对象
var asset2 = Asset.init();
asset2.name = "but different";
asset2.id = 2;
asset2.save();
详细是怎么实现的先不要急,一步一步来看。
构建对象关系映射(ORM)
对象关系映射(Object-relational mapper)是一种常见的数据结构,本质上讲,ORM是一个包装了一些数据的对象层。比方上面的Asset,就是对用户数据的包装。
首先,创建一个Model对象,Model对象用于创建新模型的实例。
var Model = {
//返回继承自Model的对象(创建一个新模型)
create: function () {
var object = Object.create(this);
object.parent = this;
object.prototype = object.fn = Object.create(this.prototype);
return object;
},
//返回继承自Model的原型的对象(实例化一个模型)
init: function () {
var instance = Object.create(this.prototype);
//实例能够通过parent属性訪问其Model
instance.parent = this;
//进行初始化
instance.init.apply(instance, arguments);
return instance;
},
//Model的原型对象
prototype: {
//初始化操作
init: function () {
}
}
}
这个Model定义了create方法和init方法。像上面说的。create用于创建新模型。init方法用于创建一个模型实例,例如以下:
var Asset = Model.create();
var User = Model.create();
var asset = Asset.init();
var user = User.init();
(注)Object.create
上面代码重点在于Object.create()这种方法。这种方法返回一个对象。返回对象继承自方法接收的參数。假设没有參数。则继承自Object,如:
var src = {
name: "zhangsan"
}
var dest = Object.create(src);
console.log(dest.name); //"zhangsan"
关于对象和其prototype对象之间的关系,能够查看前面的学习笔记:Javascript继承。
(注解完)
加入ORM属性
书中继续为Model创建了两个方法,extend和include。前者是为模型加入对象属性,后者为模型加入实例属性,例如以下:
var Model = {
/*省略create、init等方法*/
//加入对象属性
extend: function (o) {
var extended = o.extended;
//jQuery中extend的使用见后面注解
$.extend(this, o);
if (extended) {
//假设參数有这个属性。将Model传给它并调用它(回调)
extended(this);
}
},
//加入实例属性
include: function (o) {
var included = o.included;
$.extend(this.prototype, o);
if (included) {
included(this);
}
}
}
加入完之后。能够这样使用:
Model.extend({
find: function(){
console.log("find");
}
})
Model.include({
init: function(){
console.log("init");
},
load: function(){
console.log("load");
}
});
对象属性意思是直接通过模型訪问的,实例属性意思是通过实例訪问,比方:
var Asset = Model.create();
Asset.find(); //"find"
var asset = Model.init(); //"init" Model的init方法中会调用实例的init方法。(见Model里init方法的实现)
asset.init(); //"init"
asset.load(); //"load"
(注)jQuery.extend()
extend方法能够将多个对象合并到一个对象中并返回。
一、$.extend(dest, src1, src2, src3,…)
将src1、src2以及后面的对象合并到dest对象中。返回合并后的dest(后面的对象中假设有前面对象内同名的属性。后面的对象属性值将覆盖前面的)
var dest = {
name: "张三",
age: 20
};
var src1 = {
school: "蓝翔"
}
var src2 = {
name: "狗蛋",
school: "新东方"
}
var obj = $.extend(dest, src1, src2);
console.log(obj === dest); //true
console.log(obj); //{"name":"狗蛋","age":20,"school":"新东方"}
所以通过这种方法使用后dest的结构会改变。假设不想改变dest的结构。能够让第一个參数是一个空对象,即另外一种用法:
二、$.extend({}, dest, src1, src2, …)
var obj = $.extend({}, dest, src1, src2);
console.log(obj); //{"name":"狗蛋","age":20,"school":"新东方"}
这样输出也是一样的,只是dest的结构没有改变。
三、$.extend(src1)
通过这种方式调用,src1将被合并到全局变量中去。
$.extend(src1);
console.log($.school); //"蓝翔"
四、$.extend(boolean, dest, src1, src2)
当extend第一个參数是一个布尔型的时候,它将代表是否要进行深度拷贝,true代表进行深度拷贝。即将对象的子对象也进行合并。
当设置为false时。子对象将直接被覆盖。
var dest = {
name: "张三",
age: 20,
girlFriend: {
name: "小丽",
school: "清华"
}
};
var src1 = {
school: "蓝翔",
girlFriend: {
weight: 100,
height: 165
}
}
var obj = $.extend(false, dest, src1);
console.log(obj.girlFriend); //{"weight":100,"height":165}
设置为true时,对子对象也进行合并
var obj = $.extend(true, dest, src1);
console.log(obj.girlFriend); //{"name":"小丽","school":"清华","weight":100,"height":165}
对于前三种用法,根本没有第一个參数。都没有进行深度拷贝。
(注解完)
回到主题,Model的extend方法中是jQuery.extend(this, o)
,所以是将參数合并到Model中,是Model的方法。include是jQuery.extend(this.prototype, o)
,所以是将參数合并到Model的原型对象上,是实例的方法。
持久化记录
我们如今能够创建多种模型。也能为一种模型创建多个实例,如今须要做的是将记录持久化,也就是将创建的实例保存起来。
我们在Model中加入一个records对象用于保存实例,当创建一个实例时,指定其id,存入records内;当删除实例的时候。就将实例从records中删除。
//用于保存资源的对象
Model.records = {};
//由于是实例的方法。所以调用include
Model.include({
//是否为新对象
newRecord: true,
//实例的创建
create: function(){
this.newRecord = false;
//加入进Model的records中(实例能够通过parent属性訪问Model,见Model的init方法实现)
this.parent.records[this.id] = this;
},
//实例的销毁
destroy: function(){
delete this.parent.records[this.id];
},
//更新实例
update: function(){
this.parent.records[this.id] = this;
}
})
这样保存新实例的时候调用create。更新的时候调用update。为了方便,我们将其功能合并,使用save方法来保存实例,这样就不必推断是新实例还是已经保存过的实例了:
Model.include({
//保存实例
save: function(){
this.newRecode ? this.create() : this.update();
}
});
最后。保存好实例后。我们须要通过模型来找到这个元素,为Model定义一个find方法,依据指定id寻找数据对象:
Model.extend({
//通过id寻找
find: function(id){
var record = this.records[id];
if(!record){
throw("Unkown record");
}
return record;
}
});
至此。一个主要的ORM已经创建成功。我们能够试着执行一下:
var Asset = Model.create();
var asset = Asset.init();
asset.name = "same, same";
asset.id = 1;
asset.save();
var asset2 = Asset.init();
asset2.name = "but different";
asset.id = 2;
asset.save();
console.log(Asset.find("1").name); //"same, same"
asset2.name = "change";
asset2.save();
console.log(Asset.find("2").name); //"change"
添加id支持
眼下我们每次保存记录的时候都是手动指定id。实在是非常糟糕的做法。但我们能够加入自己主动化处理,书中提供了一个生成随机树的GUID:(不明觉厉)
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();
};
这样有了生成一个随机字符串的方法,我们将其加入到create方法中:
//实例的创建(见前面Model.include中代码)
create: function(){
//为其随机分配id
if(!this.id){
this.id = Math.guid();
}
this.newRecord = false;
this.parent.records[this.id] = this;
}
这样便不用操心其id了:
var asset = Asset.init();
asset.save();
console.log(asset.id); //"A65AFEB6-CB51-44F4-9E54-77A617E69903"
寻址引用
我们的ORM事实上另一个问题,在Model的find方法中,我们直接返回了实例的引用。而不是它的复制。所以find之后对其属性的改动都会直接影响原始资源,这不是我们想要的。我们希望仅仅有当调用save/update方法时才改变资源的值:
var asset = Asset.init();
asset.name = "我是原始资源";
asset.save();
console.log(Asset.find(asset.id).name); //"我是原始资源"
asset.name = "我改动了原始资源";
console.log(Asset.find(asset.id).name); //"我改动了原始资源"
asset的name被改动了,然而并没有调用save或update方法,为了避免这个问题,我们能够在find方法内创建一个新对象。并返回这个新对象。创建和更新也是一样:
Asset.extend({
//通过id寻找
find: function(id){
var record = this.records[id];
if(!record){
throw("Unkown 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 $.extend(true, {}, this);
}
});
初始化records
另一个问题是records是被全部模型共享的对象,所以无论是Asset也好User也好都公用一个records,这当然是不能接受的。我们能够在一个模型初始化的时候为其初始化自己的records。
Model.extend({
create: function(){
/*省略其它代码*/
this.records = {};
}
})
原书上是创建了一个created方法,在created方法内初始化records,created方法在create方法内调用。道理也是一样的。
后面另一些为ORM加入本地存储的方法,ORM创建好了之后事实上道理都是一样的。就不再赘述了。明天继续看Controller。好好加油。
Javascript MVC 学习笔记(一) 模型和数据的更多相关文章
- .NET MVC 学习笔记(六)— 数据导入
.NET MVC 学习笔记(六)—— 数据导入 在程序使用过程中,有时候需要新增大量数据,这样一条条数据去Add明显不是很友好,这时候最好就是有一个导入功能,导入所需要的数据,下面我们就一起来看一下导 ...
- Javascript MVC 学习笔记(三) 视图和模板
模板 Javascript中模板的核心概念是,将包括模板变量的HTML片段和Javascript对象做合并.把模板变量替换为对象中的属性值. 书中讲到了几种库作为模板引擎,可是链接失效了.能够在这里下 ...
- Javascript MVC 学习笔记(二) 控制器和状态
今天进入第二个部分:控制器. 控制器和状态 从以往的开发经验来看.我们都是将状态保存在server的session或者本地cookie中,但Javascript应用往往被限制在单页面,所以我们也能够将 ...
- ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则
ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...
- Spring MVC 学习笔记11 —— 后端返回json格式数据
Spring MVC 学习笔记11 -- 后端返回json格式数据 我们常常听说json数据,首先,什么是json数据,总结起来,有以下几点: 1. JSON的全称是"JavaScript ...
- MVC学习笔记(三)—用EF向数据库中添加数据
1.在EFDemo文件夹中添加Controllers文件夹(用的是上一篇MVC学习笔记(二)—用EF创建数据库中的项目) 2.在Controllers文件夹下添加一个空的控制器(StudentsCon ...
- ASP.NET MVC 学习笔记-2.Razor语法 ASP.NET MVC 学习笔记-1.ASP.NET MVC 基础 反射的具体应用 策略模式的具体应用 责任链模式的具体应用 ServiceStack.Redis订阅发布服务的调用 C#读取XML文件的基类实现
ASP.NET MVC 学习笔记-2.Razor语法 1. 表达式 表达式必须跟在“@”符号之后, 2. 代码块 代码块必须位于“@{}”中,并且每行代码必须以“: ...
- .NET MVC 学习笔记(五)— Data Validation
.NET MVC 学习笔记(五)—— Data Validation 在实际应用中,我们需要对数据进行增查改删业务,在添加和修改过程中,无论你编写什么样的网页程序,都需要对用户的数据进行验证,以确数据 ...
- .NET MVC 学习笔记(三)— MVC 数据显示
. NET MVC 学习笔记(三)—— MVC 数据显示 在目前做的项目中,用的最多的数据展示控件就是table展示(说不是的请走开,不是一路人),以下详细阐述下table的使用方法. 先看效果: 上 ...
随机推荐
- Payment相关逻辑
payment相关逻辑 付款有手动付款和计划程序自动付款两种,照例先列出涉及到的概念 付款方式 - PaymentTypes - 现金,支票,信用卡,等等 记账类型 - Ledger_AccTrans ...
- Python学习笔记二:函数式编程
1:Python中,内置函数名相当于一个变量,指向内置函数.所以可以通过函数名调用相应函数,也可以给函数名赋值,改变它的内容,如:可以把另一个函数变量赋值给它,那它就指向了所赋值的函数了. 2:高级函 ...
- Maven的坐标与资源库
在Maven世界中,每个工程都有它唯一的 组织名.模块名.版本 ,这三个就是maven项目的坐标,一个maven工程可以打包成jar.war.pom等形式,但是它们都是拥有上述三个坐标的.我们在项目过 ...
- 〖Linux〗Ubuntu13.10搭建文件共享Samba服务器
1. 安装 $ sudo apt-get install samba 2. 配置smb用户密码 # cat /etc/passwd | mksmbpasswd > /etc/samba/smbp ...
- MySQL存储引擎--MYSIAM和INNODB引擎区别
参考:http://blog.csdn.net/memray/article/details/8914042 MYSIAM和INNODB引擎区别主要有以下几点: 1.MyISAM查询性能比InnoDB ...
- Jmeter-maven-plugin高级配置之选择测试脚本(转)
Posted on 2014 年 6 月 6 日 在pom.xml文件中可以指定运行哪些jmx脚本. 运行所有的测试脚本 Jmeter默认运行${project.base.directory}/src ...
- VSTS 免费代码git/tfs托管体验-使用代码云托管
虽然各种代码托管平台很多.真正免费的私有仓储 却很少.微软的东西还是值得一用.免费版,5个用户.够了. 申请地址: https://www.visualstudio.com/zh-hans/free- ...
- rhel7.x配置本地yum
转载:http://www.mvpbang.com/articles/2017/12/22/1513948827684.html rhel7.x配置本地yum 环境: centos7.4 vmarew ...
- Ubuntu18.04下的 Android Studio 3.1.2
Android Studio安装 参考官网上的安装说明 # 安装依赖 :i386 lib32z1 libbz2-1.0:i386 安装openjdk (Update 2018-08-21: 这次重装U ...
- 51单片机和Arduino—闪烁灯实现
技术:51单片机学习.Keil4环境安装.Arduino环境安装.闪烁灯教程 概述 本文提供51单片机.Arduino单片机入门软件安装和一些需要使用的软件介绍,为后续单片机.嵌入式开发做 ...