一、应用场景
为了改善酷版139邮箱的代码结构,引入backbone的事件机制,按照MVC的分层思想搭建酷版云邮局的代码框架。力求在保持酷版轻量级的基础上提高代码的可维护性。
二、遗留问题
1、backbone的升级问题,新的特性无法引入
2、backbone中的潜在BUG,若官方已修复则无法同步更新
解决办法:
关注backbone官网的更新记录。
三、核心源码
/**
* @裁剪版backbone,仅包含Events、Model模块,适用于轻量级的移动终端APP
* window.Minibone
*/
(function(){
var root = this; var array = [];
var push = array.push;
var slice = array.slice;
var splice = array.splice; var ArrayProto = Array.prototype;
var nativeIsArray = Array.isArray;
var nativeForEach = ArrayProto.forEach; var ObjProto = Object.prototype;
var toString = ObjProto.toString;
var hasOwnProperty = ObjProto.hasOwnProperty; var Minibone = root.Minibone = {};
var _ = root._ = {}; var breaker = {}; _.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
}; // Fill in a given object with default properties.
_.defaults = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
}; _.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
}; // Internal recursive comparison function.
function eq(a, b, stack) {
if(typeof a === 'object' || typeof b === 'object'){
throw new Error('Function eq only support basic data types:number,string,boolean');
}
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
case '[object String]':
return a == String(b);
case '[object Number]':
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Boolean]':
return +a == +b;
}
return true;
} // only support basic data types:number,string,boolean
_.isEqual = function(a, b) {
return eq(a, b, []);
}; _.isEmpty = function(obj) {
if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
}; _.isObject = function(obj) {
return obj === Object(obj);
}; _.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
}; _.isString = function(obj) {
return toString.call(obj) == '[object String]';
}; _.result = function(object, property) {
if (object == null) return null;
var value = object[property];
return _.isFunction(value) ? value.call(object) : value;
}; _.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
}; // The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
_.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
}; _.extend = function(obj) {
_.each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
}; var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
}; // Minibone.Events
var triggerEvents = function(obj, events, args) {
var ev, i = -1, l = events.length;
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
}; var Events = Minibone.Events = {
on: function(name, callback, context) {
this._events || (this._events = {});
var list = this._events[name] || (this._events[name] = []);
list.push({callback: callback, context: context, ctx: context || this});
return this;
}, off: function(name, callback, context) {
var list, ev, events, names, i, l, j, k;
if (!this._events) return this;
if (!name && !callback && !context) {
this._events = {};
return this;
} names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
if (list = this._events[name]) {
events = [];
if (callback || context) {
for (j = 0, k = list.length; j < k; j++) {
ev = list[j];
if ((callback && callback !== (ev.callback._callback || ev.callback)) ||
(context && context !== ev.context)) {
events.push(ev);
}
}
}
this._events[name] = events;
}
} return this;
}, trigger: function(name) {
if (!this._events) return this;
var args = slice.call(arguments, 1);
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(this, events, args);
if (allEvents) triggerEvents(this, allEvents, arguments);
return this;
}
}; _.extend(Minibone, Events); // Minibone.Model
var Model = Minibone.Model = function(attributes, options) {
var defaults;
var attrs = attributes || {};
this.cid = _.uniqueId('c');
this.changed = {};
this.attributes = {};
this._changes = [];
if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attrs = this.parse(attrs);
if (defaults = _.result(this, 'defaults')) _.defaults(attrs, defaults);
this.set(attrs, {silent: true});
this._currentAttributes = _.clone(this.attributes);
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
}; _.extend(Model.prototype, Events, { changed: null,
idAttribute: 'id',
initialize: function(){}, get: function(attr) {
return this.attributes[attr];
}, // Set a hash of model attributes on the object, firing `"change"`. This is
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
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 = {});
if (!this._validate(attrs, options)) return false;
unset = options.unset;
silent = options.silent;
changes = [];
changing = this._changing;
this._changing = true; if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // 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;
} // Trigger all relevant attribute changes.
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);
}
} 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;
}, _validate: function(attrs, options) {
if (!options.validate || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
return false;
} }); // Helpers
var extend = function(protoProps, staticProps) {
var parent = this;
var child; if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
} _.extend(child, parent, staticProps); var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate; if (protoProps) _.extend(child.prototype, protoProps);
child.__super__ = parent.prototype; return child;
}; Model.extend = extend;
}).call(this);