码迷,mamicode.com
首页 > 编程语言 > 详细

Javascript MVC 学习笔记(一) 模型和数据

时间:2015-07-24 20:55:19      阅读:234      评论:0      收藏:0      [点我收藏+]

标签:javascript   mvc   

写在前面

最近在看《MVC的Javascript富应用开发》一书,本来是抱着一口气读完的想法去看的,结果才看了一点就傻眼了:太多不懂的地方了。只好看一点查一点,一点一点往下看吧,进度虽慢但也一定要坚持看完。本学习笔记是对书上所讲解内容的理解和记录。
笔记里的代码大多会按书上摘录下来,因为《MVC的Javascript富应用开发》是结合了JQuery库,所以对于JQuery中不太懂的知识点也会附在代码后面,也算是一些额外的收获。

MVC概述

要学习MVC,首先得知道MVC是什么,MVC有三个字母,代表了它将应用划分为了三个部分:数据(模型Model)、展现层(视图View)和用户交互层(控制器Controller)
对于采用MVC模式搭建的应用,一个事件的发生是这样的过程:

  1. 用户和应用产生交互
  2. 控制器的事件处理器被触发
  3. 控制器从模型中请求数据,并将其交给视图
  4. 视图将数据呈现给用户

MVC的目的在于将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

介绍完MVC,就开始分别学习MVC三个组成部分了。

模型

模型用来存放应用的所有数据对象,它只需包含数据以及直接和这些数据相关的逻辑。像其他什么事件监听、视图模版都应该隔离在模型之外。当控制器从服务器抓取数据或创建新的记录时,它就将数据包装成模型实例

比如你的应用里可能有很多用户相关的数据:比如从服务器拿到的用户的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 学习笔记(一) 模型和数据

标签:javascript   mvc   

原文地址:http://blog.csdn.net/sunhengzhe/article/details/47043775

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!