版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/billll/article/details/78949545
Backbone继承系统
Backbone的继承机制使用es3规范,可以兼容到ie8。继承接口十分简洁。
var childClass = BaseClass.extend(protoProps /* 子类prototype属性 */, classProps /* 子类静态属性 */)
先看使用方法
// 继承基类
var extendBase = require('../utils/extendBase');
// 继承方法一:通过调用父类的构造方法
var parentModel1 = extendBase.extend({
// 对象中的方法将被添加到子类的prototype上
funA: function(options) {
console.info('this is a function on prototype');
}
}, {
// 这个方法将被添加到子类的构造器上
staticFun: function() {
console.info('this is a static function');
}
});
// 使用继承方法一
var p1 = new parentModel1();
p1.funA(); // this is a function on prototype
parentModel1.staticFun(); // this is a static function
// 继承方法二:子类自己定义构造方法
var parentModel2 = extendBase.extend({
// 定义子类的构造方法,如果参数中包含constructor,那么基类的构造器不会被调用
constructor: function(){
console.info('this is the constructor of parentModel2')
},
// 对象中的方法将被添加到子类的prototype上
funA: function() {
console.info('this is a function');
}
// 这个方法将被添加到子类的构造器上
staticFun: function() {
console.info('this is a static function');
}
});
// 使用继承方法二
var p2 = new parentModel2(); // this is the constructor of parentModel2
p2.funA(); // this is a function
parentModel2.staticFun(); // this is a static function
Backbone继承的实现原理
var extend = function(protoProps, classProps) {
return inherits(this, protoProps, classProps);
};
var inherits = function(parent, protoProps, staticProps) {
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 && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
}
// 调用父类构造方法
else {
child = function() {
parent.apply(this, arguments);
};
}
// Inherit class (static) properties from parent.
// 父类静态方法继承
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
// 最难理解的地方:这么做的好处是第一避免调用基类构造函数,第二消除了子类原型链上的实例属性
var ctor = function() {}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
Backbone事件系统
Backbone的事件系统可以扩展的任何对象上,被扩展的对象具有on、off、trigger等方法,可以用来发送和监听事件。事件模块同样支持ie8。
使用方法
// 测试回调事件
var callback_listen_test = function(e) {
console.info('callback_listen_test', e);
};
var callback_on_test = function(e) {
console.info('callback_on_test', e);
};
// 定义模型对象和视图对象
var Model = _.extend({
init: function() {
// 监听Model对象触发的onTest事件
this.on('onTest', callback_on_test);
},
print: function() {
// Model对象触发listenToTest事件和onTest事件
this.trigger('listenToTest', {arg:'test'});
this.trigger('onTest', {arg:'test'});
},
// 卸载onTest事件的监听
this.off('onTest', callback_on_test);
}
}, Events);
var View = _.extend({
init: function(m) {
this.m = m;
// 监听m对象发出的listenToTest事件
this.listenTo(m, 'listenToTest', callback_listen_test);
},
stopListen: function() {
var self = this;
// 卸载对m对象的事件监听
this.stopListening(self.m, 'listenToTest', callback_listen_test);
}
}, Events);
Model.init();
View.init(Model);
// 事件触发
Model.print();
// 事件删除
View.stopListen();
Model.Off();
Model.print();
Backbone事件系统的实现原理
var Events = {
// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
on: function(name, callback, context) {
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
this._events || (this._events = {});
var events = this._events[name] || (this._events[name] = []);
events.push({callback: callback, context: context, ctx: context || this});
return this;
},
// Bind an event to only be triggered a single time. After the first time
// the callback is invoked, it will be removed.
once: function(name, callback, context) {
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
var self = this;
var once = _.once(function() {
self.off(name, once);
callback.apply(this, arguments);
});
once._callback = callback;
return this.on(name, once, context);
},
// Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
off: function(name, callback, context) {
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
// Remove all callbacks for all events.
if (!name && !callback && !context) {
this._events = void 0;
return this;
}
var names = name ? [name] : _.keys(this._events);
for (var i = 0, length = names.length; i < length; i++) {
name = names[i];
// Bail out if there are no events stored.
var events = this._events[name];
if (!events) continue;
// Remove all callbacks for this event.
if (!callback && !context) {
delete this._events[name];
continue;
}
// Find any remaining events.
var remaining = [];
for (var j = 0, k = events.length; j < k; j++) {
var event = events[j];
if (
callback && callback !== event.callback &&
callback !== event.callback._callback ||
context && context !== event.context
) {
remaining.push(event);
}
}
// Replace events if there are any remaining. Otherwise, clean up.
if (remaining.length) {
this._events[name] = remaining;
} else {
delete this._events[name];
}
}
return this;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(name) {
if (!this._events) return this;
var args = Array.prototype.slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
},
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
// listen to an event in another object ... keeping track of what it's
// listening to.
listenTo: function(obj, name, callback) {
var listeningTo = this._listeningTo || (this._listeningTo = {});
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
listeningTo[id] = obj;
if (!callback && typeof name === 'object') callback = this;
obj.on(name, callback, this);
return this;
},
listenToOnce: function(obj, name, callback) {
if (typeof name === 'object') {
for (var event in name) this.listenToOnce(obj, event, name[event]);
return this;
}
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, length = names.length; i < length; i++) {
this.listenToOnce(obj, names[i], callback);
}
return this;
}
if (!callback) return this;
var once = _.once(function() {
this.stopListening(obj, name, once);
callback.apply(this, arguments);
});
once._callback = callback;
return this.listenTo(obj, name, once);
},
// Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
stopListening: function(obj, name, callback) {
var listeningTo = this._listeningTo;
if (!listeningTo) return this;
var remove = !name && !callback;
if (!callback && typeof name === 'object') callback = this;
if (obj) (listeningTo = {})[obj._listenId] = obj;
for (var id in listeningTo) {
obj = listeningTo[id];
obj.off(name, callback, this);
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
}
return this;
}
};
// Regular expression used to split event strings.
var eventSplitter = /\s+/;
// Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventsApi = function(obj, action, name, rest) {
if (!name) return true;
// Handle event maps.
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
}
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, length = names.length; i < length; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
}
return true;
};
// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
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, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
}
};