2013年8月

第三章:模型和数据《Developing JavaScript Web Applications》学习笔记

MVC 和命名空间

引入MVC 模式,数据管理则归入模型(MVC 中的M)。模型应当从视图和控制器中解耦出来。与数据操作和行为相关的逻辑都应当放入模型中,通过命名空间进行管理。
在JavaScript 中,我们通过给对象添加属性来管理一个命名空间,这个命名空间可以是函数,也可以是变量,比如:

var User = {
records: [ /* ... */ ]
};

User 的数组数据就在命名空间User.records 中。和user 相关的函数也可以放入User 模型的命名空间里。比如,我们使用fetchRemote() 函数来从服务器端获取user 的数据:

var User = {
records: [],
fetchRemote: function(){ /* ... */ }
};

将模型的属性保存至命名空间中的做法可以确保不会产生冲突,这也是符合MVC 原则
的,同时也能避免你的代码变成一堆函数和回调混杂在一起的大杂烩。

原型继承

这里使用Object.create() 来构造我们的ORM,这和在第1 章中提到的基于类的例子有一点不同。这里使用基于原型(prototype-based)的继承,而没有用到构造函数和new关键字。
Object.create() 只有一个参数即原型对象,它返回一个新对象,这个新对象的原型就是传入的参数。换句话说,传入一个对象,返回一个继承了这个对象的新对象。Object.create() 最近才添加进了ECMAScript 第5 版规范,因此在有些浏览器中并未
实现,比如IE。但这并不是什么大问题,如果需要的话,我们可以很容易地模拟出这个函数:

if (typeof Object.create !== "function")
Object.create = function(o) {
function F() {}
F.prototype = o;
return new F();
};

现在来创建Model 对象,Model 对象将用于创建新模型和实例:

    var Model = {
    inherited: function(){
   //console.dir(this);
  },
    created: function(){
   // console.dir(this);
  },
    prototype: {
        init: function(){}
    },
    create: function(){
      var object = Object.create(this);
      object.parent = this;
      object.prototype = object.fn = Object.create(this.prototype);
      object.created();
      this.inherited(object);
      return object;
    },
    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;
        for(i in o){
        this[i]=o[i];
      }
        if (extended) extended(this);
    },
    include: function(o){
        var included = o.included;
            for(i in o){
        this.prototype[i]=o[i];
      }
        if (included) included(this);
    }
};

添加ORM 属性

现在如果给Model 对象添加属性,对于继承的模型来说,这些新增属性都是可访问的:

 Model.include({
        init: function(atts) {
            if (atts) this.load(atts);
        },
        load: function(attributes){
        for(var name in attributes)
        this[name] = attributes[name];
        }
    });

持久化记录

我们需要一种保持记录持久化的方法,即将引用保存至新创建的实例中以便任何时候都能访问它。我们通过在Model 中使用records 对象来实现。当我们保存一个实例的时候,就将它添加进这个对象中;当删除实例时,和将它从对象中删除:

Model.extend({
created: function(){
this.records = {}; 
   this.attributes=[];
}
});
Model.include({
   newRecord: true,
   create:function(){
     this.newRecord=false;
     if(!this.id)this.id=Math.guid();
     this.parent.records[this.id]=this.dup();
  },
  update:function(){
    this.parent.records[this.id]=this.dup();
  },
  destroy:function(){
    delete this.parent.records[this.id];
  },
  save:function(){
    this.newRecord?this.create():this.update();
  },
  dup:function(){
    return $.extend(true,{},this);
  }
});

寻址引用

Model.extend({
  // 通过ID查找
  find: function(id){
    var record=this.records[id];  
    return record.dup();
  }
});

向ORM中添加记录

向ORM中添加数据非常简单。我们只需从服务器抓取数据并更新模型的记录即可。现在给Model 对象增加populate() 函数,它会对任何给定的值做遍历、创建实例并更新records 对象:

//向ORM 中添加记录    
Model.extend({
      populate:function(datas){
        this.records={};
        for(var i=0,len=datas.length;i<len;i++){
          var record=this.init(datas[i]);
          record.newRecord=false;
          this.records[record.id]=record;
        }
      }
    });

现在我们可以使用Model.populate() 函数,传入请求的返回数据:

jQuery.getJSON("/assets", function(result){
Asset.populate(result);
});

本地存储数据

可以使用localStorage 和sessionStorage 对象来分别访问并操作local storage 和session storage。设置属性的操作和JavaScript 中的对象操作一样,这两个对象的设置属性操作一模一样:

// 设置一个值
localStorage["someData"] = "wem";
WebStorage API 还提供了一些新的特性:
// 存储数据的个数
var itemsStored = localStorage.length;
// 设置一个项(是一种hash 写法)
localStorage.setItem("someData", "wem");
// 得到一个已经存储的项,如果不存在则返回null
localStorage.getItem("someData"); //=> "wem";
// 删除一个项,如果不存在则返回null
localStorage.removeItem("someData");
// 清空本地存储
localStorage.clear();

数据均存储为字符串,所以如果你想保存的数据是对象或数字,则必须自己做类型转换,如果使用JSON 的话,则需要将对象先做序列化处理再保存它们,从本地存储中读取JSON 时也需要将它转换为对象:

var object = {some: "object"};
// 序列化并保存一个对象
localStorage.setItem("seriData", JSON.stringify(object));
// 读取并将JSON 转换为对象
var result = JSON.parse(localStorage.getItem("seriData"));

如果你存储的数据超出了上限(通常是每个域名5MB),再保存额外数据时则会抛出QUOTA-EXCEEDED-ERR 异常。

给ORM 添加本地存储

Model.include({
  attributes:function(){
    var result={};
    for(var i in this.parent.attributes){
      var attr=this.parent.attributes[i];
      result[attr]=this[attr];
    }
    result.id=this.id;
    return result;
  },
  //JSON.stringify()会序列化所有属性,实现toJSON() 方法就能覆盖JSON.stringify() 。
  toJSON:function(){
    return this.attributes();
  }
});
var LocalStorage={
  saveLocal:function(name){
    var result=[];
    for(var i in this.records){
      result.push(this.records[i]);
    }
    localStorage[name]=JSON.stringify(result);
  },
  loadLocal:function(name){
    var result = JSON.parse(localStorage[name]);
    this.populate(result);
  }
};
Model.extend(LocalStorage);

将新记录提交给服务器

Model.include({
createRemote: function(url, callback){
$.post(url, this.attributes(), callback);
},
updateRemote: function(url, callback){
$.ajax({
url: url,
data: this.attributes(),
success: callback,
type: "PUT"
});
}
});

一切尽在代码中


var Model = {
    inherited: function(){
   //console.dir(this);
  },
    created: function(){
   // console.dir(this);
  },
    prototype: {
        init: function(){}
    },
    create: function(){
      var object = Object.create(this);
      object.parent = this;
      object.prototype = object.fn = Object.create(this.prototype);
      object.created();
      this.inherited(object);
      return object;
    },
    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;
        for(i in o){
        this[i]=o[i];
      }
        if (extended) extended(this);
    },
    include: function(o){
        var included = o.included;
            for(i in o){
        this.prototype[i]=o[i];
      }
        if (included) included(this);
    }
};
//自动生成ID方法
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({
created: function(){
this.records = {};
this.attributes=[];
}
});

//向ORM 中添加记录
Model.extend({
populate:function(datas){
this.records={};
for(var i=0,len=datas.length;i<len;i++){
var record=this.init(datas[i]);
record.newRecord=false;
this.records[record.id]=record;
}
}
});

Model.extend({
// 通过ID查找,找不到则抛出异常
find: function(id){
var record=this.records[id];
return record.dup();
}
});

Model.include({
init: function(atts) {
if (atts) this.load(atts);
},
load: function(attributes){
for(var name in attributes)
this[name] = attributes[name];
}
});
Model.include({
newRecord: true,
create:function(){
this.newRecord=false;
if(!this.id)this.id=Math.guid();
this.parent.records[this.id]=this.dup();
},
update:function(){
this.parent.records[this.id]=this.dup();
},
destroy:function(){
delete this.parent.records[this.id];
},
save:function(){
this.newRecord?this.create():this.update();
},
dup:function(){
return $.extend(true,{},this);
}

});
Model.include({
attributes:function(){
var result={};
for(var i in this.parent.attributes){
var attr=this.parent.attributes[i];
result[attr]=this[attr];
}
result.id=this.id;
return result;
},
toJSON:function(){
return this.attributes();
}
})
var LocalStorage={
saveLocal:function(name){
var result=[];
for(var i in this.records){
result.push(this.records[i]);
}
localStorage[name]=JSON.stringify(result);
},
loadLocal:function(name){
var result = JSON.parse(localStorage[name]);
this.populate(result);
}
};
Model.extend(LocalStorage);
Model.include({
createRemote: function(url, callback){
$.post(url, this.attributes(), callback);
},
updateRemote: function(url, callback){
$.ajax({
url: url,
data: this.attributes(),
success: callback,
type: "PUT"
});
}
});

//拷贝Model
var User=Model.create();
//console.log(User);
User.attributes=['id','name'];
var user1=User.init({id:1,name:"heyong"});
user1.save();

var user2=User.init({id:2,name:"lilei"});
user2.save();

var f_user1=User.find(1);
f_user1.name="22";
user2.name="33";
/*console.log(User);
*/
var Asset=Model.create();
Asset.attributes=['id','title'];

var asset1=Asset.init({id:1,title:"1111标题唉唉唉"});

var asset2=Asset.init({id:2,title:"2222标题唉唉唉"});
asset1.save();
asset2.save();
asset2.createRemote("/asset");

Asset.saveLocal("Asset");
Asset.loadLocal("Asset");
console.log(Asset);

var attr=asset1.attributes();
console.log(attr);
var json = JSON.stringify(Asset.records);
console.log(json);

localStorage.setItem("someData", "wem");
console.log(localStorage.getItem("someData"));
//本地存储API

See the Pen sijfv by buer (@buer) on CodePen.