Backbone事件机制核心源码(仅包含Events、Model模块)
一、应用场景
为了改善酷版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);
posted on 2014-09-07 08:50 Hellohuman 阅读(287) 评论(0) 编辑 收藏 举报