backbone库学习-model

2023-01-04,,

backbone库的结构:

http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html

本文所有例子来自于http://blog.csdn.net/eagle_110119/article/details/8842007

1.1  先看model块的结构

var Model = Backbone.Model = function(attributes, options){}
_.extend(Model.prototype, Events,{..})
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
_.each(modelMethods, function(method) {})

第一个是Model的构造器,第二个是Model的原型。注意,这里将Events和一系列的自定义参数都放进了Model的原型上,backbone必须依赖一个underscore库,我们在underscore库中找到相对应的方法。

_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};

model上的方法非常多,我们先从实例化开始,先上例子

//定义Book模型类
var Book = Backbone.Model.extend({
defaults: {
name: 'unknow',
author: 'unknow',
price: 0
}
})

第一句话:

var Model = Backbone.Model = function(attributes, options){...}
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend

找到extend方法

var extend = function(protoProps, staticProps) {
var parent = this;
var child; // The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {//检查protoProps是否拥有constructor属性(不考虑原型上)
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };//借用构造器,this指向model构造器,让子类实例化时,可以获取父类构造器的成员
} // Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);//将父类和staticProps上的属性成员统统传给child的构造器上 // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;//临时构造器的方式完成继承,Surrogate属于中间件,子类实例修改不会影响父类原型,可以让子类实例获取父类原型上的成员 // Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);//将自定义信息绑定到子类原型上 // Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype; //_super_属性方便子类直接访问父类原型 return child; //返回子类构造器
};

所以我们最后得到那个Book其实是一个继承了Model的子类构造器。ok,使用它,必须要实例化它。

var javabook = new Book();

这里,我们可以在实例化的时候,传入我们的参数,如下:

var javabook = new Book({
name : 'Thinking in Java',
author : 'Bruce Eckel',
price : 395.70
})

我们看一下构造器

var Model = Backbone.Model = function(attributes, options) {
var defaults;
var attrs = attributes || {};
options || (options = {});
this.cid = _.uniqueId('c');//生成唯一id
this.attributes = {};
if (options.collection) this.collection = options.collection;
if (options.parse) attrs = this.parse(attrs, options) || {}; options._attrs || (options._attrs = attrs);//让options的属性中拥有attributes,这在插件,库中很常见的写法
if (defaults = _.result(this, 'defaults')) {//this指向Book的实例,因为defaults对象被绑定到了Book的原型上,所以this是可以访问的
attrs = _.defaults({}, attrs, defaults);//合并
}
this.set(attrs, options);//执行原型上的set方法
this.changed = {};//将changed(变化的)清空
this.initialize.apply(this, arguments);//实例化时执行
}

在进入set方法之前,系统会将你传进去的参数与原型上的默认参数进行合并,不清楚的可以看一下_.defaults方法

_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
console.log(obj[prop]);
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
}

这里我们又有了一个新的判断方法,记得在原型上的默认参数,都是unknow和0,作者这样写也可以判断,大家学习一下。经过这个过滤,留下的基本都是我们自定义的参数了。

进入set方法。

set: function(key, val, options) {
var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
} options || (options = {}); // Run validation.
// 执行自定义的validation
if (!this._validate(attrs, options)) return false;//validate为false时不能通过 // Extract attributes and options.
// 提取属性和选项
unset = options.unset;
silent = options.silent;
changes = [];
changing = this._changing;
this._changing = true;
if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
//current表示当前状态,prev表示上一个状态。
current = this.attributes, prev = this._previousAttributes;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];//检测id // For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);//将变化了属性名加入数组
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;//跟上此状态不相同的,修改为现在的值
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。
}
// Trigger all relevant attribute changes.
// silent配置用于忽略验证规则,并且它不会触发change和error等事件 if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
//console.log(current[changes[i]]);
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
} // You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
// 在所有事件结束后,触发一次change事件
if (changing) return this;
if (!silent) {
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
}
}
this._pending = false;
this._changing = false;
return this;
}

验证和silent这块,我们在例子上再说。set中很重要的一步就是处理当前状态和上一个状态,保存相应状态。

最后,我们执行

this.changed = {};//将changed(变化的)清空
this.initialize.apply(this, arguments);//实例化时执行

看一下原型上的initialize方法

initialize: function(){
}

英文注释是:Initialize is an empty function by default. Override it with your own,用到的时候,我们需要重载下。

以上完成了一个Book的实例化。

1.2   关于model实例读取数据

看个例子:

console.log(javabook.get('name'));
console.log(javabook.get('author'));
console.log(javabook.get('price'));

显示结果:

1.2.1  get方法

看一下get方法

get: function(attr) {
return this.attributes[attr];
}

留心一下我们会发现,这个this.attributes在哪出现。

current = this.attributes, prev = this._previousAttributes;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];//检测id
// For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);//将变化了属性名加入数组
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;//跟上此状态不相同的,修改为现在的值
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。
}

将this.attributes和current之间是引用传递,当current[attr]的值变化时,this.attributes中的值也发生了变化,刚开始current为一个空对象,它会根据你自定义传入的对象,去复制过来。另外attributes是实例上的属性,所以我们可以这样取值。

console.log(javabook.attributes['name'])
console.log(javabook.attributes['author'])
console.log(javabook.attributes['price'])

其实结果是一样的。

1.2.2  escape()

我们看一下,另外一种取值方法:escape()

escape: function(attr) {
return _.escape(this.get(attr));
}

看似做了层过滤,看下_.escape方法

_.each(['escape', 'unescape'], function(method) {
_[method] = function(string) {
if (string == null) return '';//为空将返回
return ('' + string).replace(entityRegexes[method], function(match) {//匹配到正则的部分执行function方法,实际上就是将<,>,&,",'等进行转换
return entityMap[method][match];
});
};
}); var entityRegexes = {
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),//拼正则
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
}; var entityMap = {//需要转换的部分
escape: {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '''
}
};

如果需要添加新的转换部分,可以添加到entityMap中。

1.3  修改数据

看例子:

var javabook = new Book();  

// 通过set方法设置模型数据
javabook.set('name', 'Java7入门经典');
javabook.set('author', 'Ivor Horton');
javabook.set('price', 88.50);
// 获取数据并将数据输出到控制台
var name = javabook.get('name');
var author = javabook.get('author');
var price = javabook.get('price'); console.log(name); // 输出Java7入门经典
console.log(author); // 输出Ivor Horton
console.log(price); // 输出88.50

原型上的set方法,刚才我们已经看过了,set方法会将我们传入的值与上一次状态的值进行比较,同样也与没赋这次值的当前值进行比较。如果改变了,则将新的值覆盖旧的值,不过this._previousAttributes中保存着model上一次状态的值。

set可以单独一个个赋值,同样也可以一起赋值

//set()方法也允许同时设置多个属性,例如:
javabook.set({
name : 'Java7入门经典',
author : 'Ivor Horton',
price : 88.50
});

1.4 修改数据吗,触发事件

当调用set()方法修改模型中的数据时,会触发一系列事件,我们常常通过监听这些事件,来动态调整界面中数据的显示,我们先来看一个例子:

// 定义Book模型类
var Book = Backbone.Model.extend({
defaults : {
name : 'unknown',
author : 'unknown',
price : 0
}
}); // 实例化模型对象
var javabook = new Book(); // 监听模型"change"事件
javabook.on('change', function(model) {
console.log('change事件被触发');
});
// 监听模型"change:name"事件
javabook.on('change:name', function(model, value) {
console.log('change:name事件被触发');
});
// 监听模型"change:author"事件
javabook.on('change:author', function(model, value) {
console.log('change:author事件被触发');
});
// 通过set()方法设置数据
javabook.set({
name : 'Thinking in Java',
author : 'unknown',
price : 395.70
}); // 控制台输出结果:
// change:name事件被触发
// change事件被触发

问题在set方法中,一般情况我们不设置slient的情况下,会执行事件,看代码:

// silent配置用于忽略验证规则,并且它不会触发change和error等事件

            if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}

如果我们修改的数据,changes数组中是会有值的。遍历changes数组,将修改过的属性名找到,执行类似'change:属性名'为名称的事件,这里,告诉我们在页面,如果想通过数据修改触发事件的话,这个事件的命名按照'change'+'属性名'来定义。

另外,set源码中,还有一段:

// 在所有事件结束后,触发一次change事件
if (changing) return this;
if (!silent) {
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
}
}

一旦数据不真正修改了,那this._pending将变为true,将默认执行一遍change方法。我们可能监听change方法来判断数据是否被修改(但是你也可以通过获取实例的_pending属性来判断)

除了get方法之外,还有两种方法获取上一次状态的值

previous()

previousAttributes()

先看previous()

previous: function(attr) {
if (attr == null || !this._previousAttributes) return null;
return this._previousAttributes[attr];
}

很简单,this._previousAttributes存放着上一个状态的参数。

previousAttributes()

previousAttributes: function() {
return _.clone(this._previousAttributes);
}

克隆一份返回,这样修改不会影响原来的状态值。

1.5  数据验证

Backbone模型提供了一套数据验证机制,确保我们在模型中存储的数据都是通过验证的,我们通过下面的例子来说明这套验证机制:

var Book = Backbone.Model.extend({
validate : function(data) {
if(data.price < 1) {
return '书籍价格不应低于1元.';
}
}
}); var javabook = new Book(); // 监听error事件,当验证失败时触发
javabook.on('error', function(model, error) {
console.log(error);
});
javabook.set('price', 0);

找到set方法中的相应方法:

if (!this._validate(attrs, options)) return false;

大家注意,上述的例子没有作用,为什么?因为this._validate()中传入的两个参数为空,定义Book时传入的validate实际上绑定到Book的原型上。实例化时根本没有传入任何数据。这里源码存在错误,看看我们该如何修改。

先看_validate方法:

_validate: function(attrs, options) {
//if (!options.validate || !this.validate) return true;//这里的this.validate是你自己定义的,所以validate需要定义在model类中
if(!this.validate || !options.validate) return true//没有验证,直接通过
attrs = _.extend({}, this.attributes, attrs);
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;//没有报错内容,返回true
this.trigger('invalid', this, error, _.extend(options, {validationError: error})); //如果是false,则表示验证正确,否则则自动执行下面的trigger方法,抛出异常
return false;
}

因为options没值,所以!options.validate恒为true,这也就是为什么validate不验证的关键。修改下判断为:

if(!this.validate || !options.validate) return true//没有验证,直接通过

继续向下,看这段代码:

this.trigger('invalid', this, error, _.extend(options, {validationError: error})); 

如果抛出错误,会执行事件名为invalid的事件,那我们再看看页面的绑定事件名,是error,不相符,导致从this._events中按事件名取事件取不到,导致验证失败。ok,简单修改下页面

// 监听error事件,当验证失败时触发
javabook.on('invalid', function(model, error) {
console.log(error);
});

ok,修改完成,我们再运行一遍,看结果

对于修改validate这块,我只是列举了一种办法,还有很多方法,你选择你喜欢。再说一句,绑定validate还有一种方式:

javabook.set('price', 0, {
error : function(model, error) {
console.log('自定义错误:' + error);
}
});

options将会有值,这样就不会跳出validate判断了。

1.6  slient配置

这个东西搁了一段没看,现在我们根据例子来过一遍。上例子:

javabook.set('price', 0, {
silent : true
});

如果传入silent,那将不触发change和change:属性名的事件,但记住,它只在定义它的时候生效,换言之,如下代码:

javabook.set('price', 0, {
silent : true
});
javabook.set('name', 'Thinking in Java');

第一次set不会抛异常,第二次会,为什么,因为传入的参数不一样。第二次没有silent,就可以触发change等事件了。

1.7  删除数据

Backbone提供了unset和clear方法,看下API

unset()方法用于删除对象中指定的属性和数据

clear()方法用于删除模型中所有的属性和数据

例子:

// 定义Book模型类
var Book = Backbone.Model.extend();
// 实例化模型对象
var javabook = new Book({
name : 'Java7入门经典',
author : 'Ivor Horton',
price : 88.50
});
// 输出: Java7入门经典
console.log(javabook.get('name'));
// 删除对象name属性
javabook.unset('name');
// 输出: undefined
console.log(javabook.get('name'));
当我们对模型的name属性执行unset()方法后,模型内部会使用delete关键字将name属性从对象中删除。 clear()方法与unset()方法执行过程类似,但clear()方法会删除模型中的所有数据,例如:
// 定义Book模型类
var Book = Backbone.Model.extend();
// 实例化模型对象
var javabook = new Book({
name : 'Java7入门经典',
author : 'Ivor Horton',
price : 88.50
}); // 删除对象name属性
javabook.clear(); // 以下均输出: undefined
console.log(javabook.get('name'));
console.log(javabook.get('author'));
console.log(javabook.get('price'));

先从unset开始

javabook.unset('name');

找到相应的unset方法

unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
}

可以看的很清楚,unset还是用到set方法,注意它传入的{unset:true}的,回到之前的set方法中,去找相应的unset部分,我们会找到这段代码。

unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。

unset为true之后,delete current[attr],即删除current['name']。表示删除了name属性。

再来看clear方法

clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;//将this.attributes中所有的成员清空,这里undefined可能会被重写,所以用void 0代替
return this.set(attrs, _.extend({}, options, {unset: true}));
}

clear方法依旧是调用set方法,由于成员被清空,再传入set中,删除掉current[attr],释放掉内存。

1.8  将模型数据同步到服务器

1.8.1  save

Backbone提供了与服务器数据的无缝连接,我们只需要操作本地Model对象,Backbone就会按照规则自动将数据同步到服务器。如果需要使用Backbone默认的数据同步特性,请确定你的服务器数据接口已经支持了REST架构。具体概念,大家可以看http://blog.csdn.net/eagle_110119/article/details/8842007部分的讲解,或者上网搜些资料看。

数据标识:Backbone中每一个模型对象都有一个唯一标识,默认名称为id,你可以通过idAttribute属性来修改它的名称。

URL: Backbone默认使用PATHINFO的方式来访问服务器接口。

先看例子

// 定义Book模型类
var Book = Backbone.Model.extend({
urlRoot : '/service'
}); // 创建实例
var javabook = new Book({
id : 1001,
name : 'Thinking in Java',
author : 'Bruce Eckel',
price : 395.70
}); // 保存数据
javabook.save();

看一下save方法

save: function(key, val, options) {
var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
} options = _.extend({validate: true}, options);
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;//验证通过了
} // Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
} // After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function(resp) {//返回成功的回调
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);//将options绑定一个error方法
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');//判断id,如果没有则新建,如果有再判断options.patch,改为更新
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options); // Restore attributes.
if (attrs && options.wait) this.attributes = attributes; return xhr;
}

这里,我们先只从前端的角度看save方法,有条件的朋友,可以搭建一个环境,与服务器交互下,效果会更好(目前我也再搞,我使用的是node+mongodb)

在save方法中,我们调用了validate进行了验证。验证不通过,则不允许发送请求。其中的options.success是一个返回成功的回调函数。看一下wrapError

var wrapError = function(model, options) {
var error = options.error;
options.error = function(resp) {
if (error) error(model, resp, options);
model.trigger('error', model, resp, options);
};
};

这个方法,给options添加了一个error方法,主要是为了防止当ajax请求失败时,捕获错误信息。

其中ajax的请求,主要包含:url,method,async,datatype,success,error等。看一下save如果处理method

method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');//判断id,如果没有则新建,如果有再判断options.patch,改为更新

接着是

xhr = this.sync(method, this, options);

ajax归根结底是通过XMLHttpRequest实例来操作的,来看如何创建一个xhr实例,进入原型的sync方法

sync: function() {
return Backbone.sync.apply(this, arguments);
}

看一下Backbone.sync

Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
_.defaults(options || (options = {}), {
emulateHTTP: Backbone.emulateHTTP,//false
emulateJSON: Backbone.emulateJSON //false
}); // Default JSON-request options.
// 默认JSON请求选项
var params = {type: type, dataType: 'json'}; // Ensure that we have a URL.
// 查看选项中是否有url,没有则选用实例set时的url
if (!options.url) {
params.url = _.result(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';//用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
params.data = JSON.stringify(options.attrs || model.toJSON(options));//转成字符串,其中model.toJSON()返回this.attributes里的信息
}
// params中包含了发送给服务器的所有信息
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (options.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
} // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
if (options.emulateJSON) params.data._method = type;
var beforeSend = options.beforeSend;
options.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
if (beforeSend) return beforeSend.apply(this, arguments);
};
} // Don't process data on a non-GET request.
if (params.type !== 'GET' && !options.emulateJSON) {
params.processData = false;
} // If we're sending a `PATCH` request, and we're in an old Internet Explorer
// that still has ActiveX enabled by default, override jQuery to use that
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
if (params.type === 'PATCH' && noXhrPatch) {
params.xhr = function() {
return new ActiveXObject("Microsoft.XMLHTTP");
};
}
// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
}

其中,整个这个方法中,主要完成的工作,就是填充params,让其包含传到服务器所需要的所有信息,包括头,编码等等。另外在ajax中存在兼容性问题,低版本的IE没有xhr对象,它们有自己的实例对象activeObject。

兼容性判断

var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

浏览器不支持xhr,可以使用activeObject

代码最后,调用了Backbone.ajax(_.extend(params,options))

Backbone.ajax = function() {
return Backbone.$.ajax.apply(Backbone.$, arguments);
}

再看

Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;

这里,我们都清楚了,Backbone在与服务器交互时,再发送请求时调用的是第三方的ajax,这里我们使用的是jQuery。最后返回xhr对象。save方法结束。(这里大家可以结合$.ajax()来理解)

例子中还将,可以这样写回调

// 将数据保存到服务器
javabook.save(null, {
success : function(model) {
// 数据保存成功之后, 修改price属性并重新保存
javabook.set({
price : 388.00
});
javabook.save();
}
});

这样也可以,为什么呢,看源码:

var success = options.success;//你可以在save方法的时候写成功回调
options.success = function(resp) {//返回成功的回调
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};

没有自己写回调,系统帮你写,如果有回调,则使用你的回调,至于回调函数,我们最后看。

还有一个wait参数的配置,看例子

// 从将数据保存到服务器
javabook.save({
name : 'Thinking in Java',
author : 'Bruce Eckel',
price : 395.70
}, {
wait : true
});

例子中的解答是如果我们传递了wait配置为true,那么数据会在被提交到服务器之前进行验证,当服务器没有响应新数据(或响应失败)时,模型中的数据会被还原为修改前的状态。如果没有传递wait配置,那么无论服务器是否保存成功,模型数据均会被修改为最新的状态、或服务器返回的数据。

我们来看回调,里面有几个方法

if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;//验证通过了
}

如果设置了wait为true,将会进行验证(其实你不传值,也要进行验证。。。)

再看这个方法

parse: function(resp, options) {
return resp;
}

对照save里的方法

model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);

如果设置了wait,当服务器没有响应时,理论上resp是没有值,serverAttrs的值应该为attrs,这个是原来修改前的值。对于ajax请求失败,失败了服务器一般返回错误信息,数据库里的数据是不会修改原来的状态,所以原来的状态依旧是原来的状态。

其实在我们不设置wait参数时,save方法可以有参数,不需要在此之前使用set,因为他包含了这set方法看例子

javabook.save({
name: 'helloWorld'
//,wait: true
}
);

运行结果为:

可以看到name被修改了,save完成了set的功能,前提,不要设置wait,wait可以理解为保持原有的参数不变(在ajax没有返回时,或者报错时)

很多时候,接口返回的数据是多种多样的,例子上有一种情况

{
"resultCode" : "0",
"error" : "null",
"data" : [{
"isNew" : "true",
"bookId" : "1001",
"bookName" : "Thinking in Java(修订版)",
"bookAuthor" : "Bruce Eckel",
"bookPrice" : "395.70"
}]
}

backbone提供了一个parse方法,不过之前我们已经看过,默认情况,这个方法提供传入两个参数,并返回第一个参数,解析时,自己重写parse方法解析。

1.8.2  fetch

fetch()方法用于从服务器接口获取模型的默认数据,常常用于模型的数据恢复,它的参数和原理与save()方法类似,因此你可以很容易理解它。

从fetch代码看起:

fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function(resp) {//成功的回调
if (!model.set(model.parse(resp, options), options)) return false;
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);//错误的回调
return this.sync('read', this, options);
}

基本跟save相似,注意,这里的标识为read。如果需要在回调中绑定响应事件的,可以在页面用on绑定事件,事件名为sync,这样是正确回调后,是可以触发的。

这里,因为没有连接上服务器,所以id的部分没有给出,抱歉。

1.8.3  destroy

destroy()方法用于将数据从集合(关于集合我们将在下一章中讨论)和服务器中删除,需要注意的是,该方法并不会清除模型本身的数据,如果需要删除模型中的数据,请手动调用unset()或clear()方法)当你的模型对象从集合和服务器端删除时,只要你不再保持任何对模型对象的引用,那么它会自动从内存中移除。(通常的做法是将引用模型对象的变量或属性设置为null值)

看一下destory

destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success; var destroy = function() { //模型会触发destroy事件,页面需要声明
model.trigger('destroy', model, model.collection, options);
}; options.success = function(resp) { //成功回调
if (options.wait || model.isNew()) destroy();
if (success) success(model, resp, options);
if (!model.isNew()) model.trigger('sync', model, resp, options);
}; if (this.isNew()) {//查看id是否是新的。
options.success();
return false;
}
wrapError(this, options);//错误回调 var xhr = this.sync('delete', this, options);
if (!options.wait) destroy();//设置wait之后,将不会触发destory
return xhr;
}

这里的标识为delete,可以看到,该方法并不会清除模型本身(也就是没有跟this.attributes打交道)。

这里基本过完一遍model。ok,好吧有点长。。

内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。

    

backbone库学习-model的相关教程结束。

《backbone库学习-model.doc》

下载本文的Word格式文档,以方便收藏与打印。