在MVC模式下,一个应用被分解为三个部分:Model(数据), View(表现层), Controller(交互层)…..
MVC是一种应用的设计模式。 在这种模式下,一个应用被分解为三个部分:Model(数据), View(表现层), Controller(交互层)。
The Model Model 应该从View和Controller分离出来, 数据和与数据相关的的逻辑和操作都应该放在Model中。并且应该正确地命名空间。 把Model的属性放在命名空间下可以防止与全局变量产生冲突 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var User = { records: [], fetchReomote: function ( ) { } } `` ` - 我们可以把实例使用的方法放在类的原型上,而与实例无关的属性则直接添加作为类的属性。 ` `` javascriptvar User= function (atts ) { this .attributes = atts || {}; }; User.prototype.destory = function ( ) { } var user = new User;user.destory();
关系对象映射器(Object-relational mapper or ORM) ORM 是数据的包装层。通过ORM连接Model和服务器, 任何对Model的改变都会通过Ajax请求反馈给服务器。 我们也可以连接Model和HTML元素, 这样Model的任何改变在View(表现层) 上也会有直观体现。 原型继承 通过Object.create()
原型继承来构建ORM. Object.create()
方法使用指定的原型对象及其属性去创建一个新的对象1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var Model = { inherited: function ( ) {}, created: function ( ) {}, 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; } }
create()
返回一个从Model继承的新对象,用来创建 新的Model类型 。init()
返回一个从Model.prototype
继承的新对象——一个Model实例。添加 ORM 属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 jQuery.extend(Model, { find: function ( ) {} }); jQuery.extend(Model.prototype, { init: function (atts ) { if (atts) this .load(atts); }, load: function (attributes ) { for ( var name in attributes ){ this [name] = attributes[name]; } } });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var Model = { inherited: function ( ) {}, created: function ( ) {}, 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); object.parent = this ; instance.init.apply(instance, arguments ); return instance; }, extend: function (o ) { var extended = o.extended; for (var a in o){ this [a] = o[a]; } if (extended) extended(this ); }, include: function (o ) { var included = o.included; for (var a in o){ this .prototype[a] = o[a]; } if (included) included(this ); } }
持久记录 我们需要一种保存记录的方法,保存创建实例的引用 , 以便稍候访问。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Model.records = {}; Model.include({ newRecord: true , create: function ( ) { this .newRecord = false ; this .parent.records[this .id] = this ; }, destory: function ( ) { delete this .parent.records[this .id]; } }); Model.include({ update: function ( ) { this .parent.records[this .id] = this ; } }); Model.include({ save: function ( ) { this .newRecord ? this .create() : this .update(); } }); Model.extend({ find: function (id ) { return this .records[id] || throw ("Unknown record" ); } });
增加 ID 支持 我们需要一种自动生成ID的方法,常见的方法是GUID(Globally Unique Identifier)全局唯一标识符生成器,但是此方法不适用于JavaScript。 Robert Kieffer 通过 Math.random()
设计了一个简易的GUID生成器 1 2 3 4 5 6 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(); }
&
和 |
执行按位运算。r初始值包含一个从0x0到0xf的随机数,用二进制表示为r = abcd。r & 0x3
执行二进制数abcd与0011的&
按位运算,结果为00cd,然后 |0x8
执行的结果为10cd
,所以r最终可能的值为1000,1001,1010,1011(二进制),最终通过.toString(16)
从16进制转化为字母。1 2 3 4 5 6 7 Model.extend({ create: function ( ) { if (!this .id) this .id = Math .guid(); this .newRecord = false ; this .parent.records[this .id] = this ; } });
处理引用问题 如果仔细观察我们的代码你会发现,当Model的一个实例属性改变后,Records里这个实例的对应属性也会自动改变(本应该执行update()
之后改变),这是由于 records 的实例和实例本身同时指向相同的对象。 我们可以复制实例本身,然后将复制的加入到records中来解决这个问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Model.extend({ find: function (id ) { var record = this .records[id]; if ( !record ) throw ('Unknown record' ); return record.dup(); } }) Model.include({ create: function ( ) { this .newRecord = false ; this .id = Math .guid(); this .parent.records[this .id] = this .dup(); }, update: function ( ) { this .parent.records[this .id] = this .dup(); }, dup: function ( ) { return jQuery.extend(true ,{},this ); } })
另一个问题是Model.records
在任何一个特定的Model子类上都可以访问。 可以去掉Model上的records, 通过Model.created()
(Model.create()
的callback)在每一个创建的Model子类上分别创建records对象。1 2 3 4 5 6 7 Model.extend( { created:function ( ) { this .records = {}; } } )
数据加载 加载数据的三种方法初始页面行内加载——增大HTTP页面大小 通过AJAX 通过 JSONP Ajax jQuery Ajax’s API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 jQuery.ajax({ url: '/ajax/endpoint' , type: 'GET' , success: function (data ) { alert(data); } }); jQuery.get('/ajax/endpoint' , function (data ) { $('.ajaxResult' ).text(data); }); jQuery.get('/ajax/endpoint' , {foo : 'bar' }, function (data ) { }); jQuery.post('/users' , {first_name : 'Alex' }, function (result ) { }) jQuery.getJSON('/json/endpoint' , function (json ) { })
同源策略
Ajax的限制来自于同源策略,同源策略限制了请求必须发送到页面发送方的域名、子域名、端口。这是因为在发送Ajax请求时,本域名下的Cookie信息会和Ajax请求一同发送给远端服务器。如果没有同源策略的限制, 不同域名的服务器就可以发访问本域名下的Cookie,获得Cookie中的重要信息,比如邮件信息,登录信息….. CORS 跨站资源共享通过CORS, 如果你想授权他人访问你的服务器, 只需在返回回复中添加如下头部, 该头部授权来自example.com的跨源GET和POST请求 1 2 Access-Control-Allow-Origin: example.com Access-Control-Request-Method: GET, POST
可以使用Access-Control-Request-Headers标头授权自定义请求标头1 Access-Control-Request-Headers: Authorization
JSONP Script标签不受同源策略限制。 实现方式: 设置一个<script></script>
其src
属性指向一个JSON端点(发送JSON数据的url), 返回的数据被封装在一个函数调用中。这种方式适用于任何浏览器。 1 2 3 4 5 <script src ="http://example.com/data.json" > jQuery.getJson('http://example.com/data.json?callback=?' , function (result ) { }) </script >
jQuery更换URL中最后一个?
为一个临时函数。服务器需要读取callback的值然后将其作为返回的封装函数 跨域请求的安全性问题 如果不确定哪些域可以通过CORS/JSONP访问你的API,要考虑如下几方面:不要暴露重要信息,与邮箱地址等。 不允许任何操作,如 Follow,Unfollow, Delete…. 填充ORM 1 2 3 4 5 6 7 8 9 10 11 12 Model.extend({ populate: function (values ) { this .records = {}; for (var i = 0 , il = values.length; i < il; i++){ var record = this .init(values[i]); record.newRecord = false ; this .records[record.id] = record.dup(); } } })
1 2 3 4 var Asset = User.create()jQuery.getJson('/assets' , function (result ) { Asset.populate(result); })
本地储存数据 ORM增加本地储存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Model.extend({ created: function ( ) { this .records = {}; this .attributes = []; } }); Asset.attributes = ['name' , 'ext' ]; 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; } }); Asset.attributes = ['name' , 'ext' ]; var asset = Asset.init({name : 'document' , ext : '.txt' });asset.attributes(); Model.include({ toJSON: function ( ) { return (this .attributes()): } })
1 2 3 4 5 6 7 8 9 10 11 12 13 var Model.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); } }
提交新的记录到服务器 1 2 3 4 5 6 7 8 9 10 11 12 13 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' }) } })
Final Version 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();
};
var Model = {
prototype: {
newRecord: true ,
init: function (atts ) {
if (atts) this .load(atts);
this .save();
},
load: function (attributes ) {
for (var name in attributes){
this [name] = attributes[name];
console .log('a' );
}
},
create: function ( ) {
if (!this .id) this .id = Math .guid();
this .newRecord = false ;
this .parent.records[this .id] = this .dup();
},
save: function ( ) {
this .newRecord ? this .create() : this .update();
},
destory: function ( ) {
delete this .parent.records[this .id];
},
update: function ( ) {
this .parent.records[this .id] = this .dup();
},
dup: function ( ) {
return jQuery.extend(true , {}, this );
},
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());
},
createRemote: function (url, callback ) {
$.post(url, this .attributes(), callback);
},
updateRemote: function (url, callback ) {
$.ajax({
url: url,
data: this .attributes(),
success: callback,
type:'PUT'
})
}
},
and grandparent of instance
inherited: function ( ) {},
created: function ( ) {
this .records = {};
this .attributes = [];
},
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;
},
find: function (id ) {
return this .records[id];
},
extend: function (o ) {
var extended = o.extended;
for (var a in o){
this [a] = o[a]
}
if (extended) extended(this );
},
include: function (o ) {
var included = o.included;
for (var a in o){
this .prototype[a] = o[a];
}
if (included) included(this );
},
populate: function (values ) {
this .records = {};
for ( var i = 0 , il = values.length; i < il; i++){
var record = this .init(values[i]);
record.newRecord = false ;
this .records[record.id] = record.dup();
}
},
saveLocal: function (name ) {
var result = [];
for ( var i in this .records){
result.push(this .records[i]);
}
console .log(result);
localStorage[name] = JSON .stringify(result);
},
loadLocal: function (name ) {
var result = JSON .parse(localStorage[name]);
this .populate(result);
}
};
The Controller Controller连接View和Model, 处理来自view的事件和输入,与model沟通并更新view.