Backbone.js混合模式 #
一、Mixin概述 #
1.1 什么是Mixin #
Mixin是一种代码复用模式,允许将功能模块混入到类中。
text
Mixin特点
├── 代码复用:提取通用功能
├── 组合优于继承:灵活组合功能
├── 单一职责:每个Mixin专注一个功能
└── 无侵入性:不修改原有类结构
1.2 Mixin vs 继承 #
| 特性 | 继承 | Mixin |
|---|---|---|
| 关系 | is-a | has-a |
| 复用 | 单一继承链 | 多个Mixin组合 |
| 灵活性 | 较低 | 较高 |
| 复杂度 | 继承层次深时复杂 | 组合灵活 |
二、基本Mixin #
2.1 简单Mixin #
javascript
var TimestampMixin = {
touch: function() {
this.set('updatedAt', new Date().toISOString());
},
getAge: function() {
var created = new Date(this.get('createdAt'));
return Date.now() - created.getTime();
}
};
var User = Backbone.Model.extend({
initialize: function() {
this.set('createdAt', new Date().toISOString());
}
});
_.extend(User.prototype, TimestampMixin);
var user = new User();
user.touch();
2.2 带初始化的Mixin #
javascript
var EventLoggerMixin = {
initializeEventLogger: function() {
this.on('all', function(eventName) {
console.log('[Event]', eventName);
});
}
};
var User = Backbone.Model.extend({
initialize: function() {
this.initializeEventLogger();
}
});
_.extend(User.prototype, EventLoggerMixin);
2.3 带状态的Mixin #
javascript
var LoadingMixin = {
isLoading: function() {
return this._loading === true;
},
setLoading: function(loading) {
this._loading = loading;
this.trigger('loading:change', loading);
}
};
var User = Backbone.Model.extend({
fetch: function(options) {
this.setLoading(true);
var self = this;
options = options || {};
var success = options.success;
options.success = function() {
self.setLoading(false);
if (success) success.apply(this, arguments);
};
return Backbone.Model.prototype.fetch.call(this, options);
}
});
_.extend(User.prototype, LoadingMixin);
三、常用Mixin #
3.1 验证Mixin #
javascript
var ValidationMixin = {
validateRequired: function(fields) {
var errors = {};
var hasError = false;
fields.forEach(function(field) {
if (!this.get(field)) {
errors[field] = field + ' is required';
hasError = true;
}
}, this);
return hasError ? errors : undefined;
},
validateEmail: function(field) {
var value = this.get(field);
if (value && !value.match(/^[\w-]+@[\w-]+\.[a-z]+$/i)) {
return 'Invalid email format';
}
},
validateMinLength: function(field, minLength) {
var value = this.get(field);
if (value && value.length < minLength) {
return field + ' must be at least ' + minLength + ' characters';
}
}
};
var User = Backbone.Model.extend({
validate: function(attrs) {
var errors = this.validateRequired(['name', 'email']);
if (!errors) {
errors = {};
}
var emailError = this.validateEmail('email');
if (emailError) errors.email = emailError;
return Object.keys(errors).length > 0 ? errors : undefined;
}
});
_.extend(User.prototype, ValidationMixin);
3.2 选择Mixin #
javascript
var SelectableMixin = {
select: function() {
if (!this._selected) {
this._selected = true;
this.trigger('selected', this);
}
},
deselect: function() {
if (this._selected) {
this._selected = false;
this.trigger('deselected', this);
}
},
toggleSelect: function() {
if (this._selected) {
this.deselect();
} else {
this.select();
}
},
isSelected: function() {
return this._selected === true;
}
};
var Item = Backbone.Model.extend({});
_.extend(Item.prototype, SelectableMixin);
3.3 过滤Mixin #
javascript
var FilterMixin = {
filterBy: function(criteria) {
return this.filter(function(model) {
for (var key in criteria) {
if (model.get(key) !== criteria[key]) {
return false;
}
}
return true;
});
},
searchBy: function(fields, query) {
query = query.toLowerCase();
return this.filter(function(model) {
return fields.some(function(field) {
var value = model.get(field);
return value && value.toLowerCase().indexOf(query) !== -1;
});
});
}
};
var Users = Backbone.Collection.extend({});
_.extend(Users.prototype, FilterMixin);
3.4 分页Mixin #
javascript
var PaginationMixin = {
page: 1,
perPage: 10,
setPage: function(page) {
this.page = page;
this.trigger('page:change', page);
},
getPage: function() {
var start = (this.page - 1) * this.perPage;
var end = start + this.perPage;
return this.slice(start, end);
},
getTotalPages: function() {
return Math.ceil(this.length / this.perPage);
},
hasNextPage: function() {
return this.page < this.getTotalPages();
},
hasPrevPage: function() {
return this.page > 1;
},
nextPage: function() {
if (this.hasNextPage()) {
this.setPage(this.page + 1);
}
},
prevPage: function() {
if (this.hasPrevPage()) {
this.setPage(this.page - 1);
}
}
};
var Users = Backbone.Collection.extend({});
_.extend(Users.prototype, PaginationMixin);
四、高级Mixin #
4.1 带生命周期的Mixin #
javascript
var DisposableMixin = {
initializeDisposable: function() {
this._disposables = [];
},
registerDisposable: function(disposable) {
this._disposables.push(disposable);
},
dispose: function() {
this._disposables.forEach(function(disposable) {
if (typeof disposable === 'function') {
disposable();
} else if (disposable.dispose) {
disposable.dispose();
}
});
this._disposables = [];
this.trigger('disposed');
}
};
var MyView = Backbone.View.extend({
initialize: function() {
this.initializeDisposable();
this.registerDisposable(
this.listenTo(this.model, 'change', this.render)
);
},
remove: function() {
this.dispose();
Backbone.View.prototype.remove.call(this);
}
});
_.extend(MyView.prototype, DisposableMixin);
4.2 带配置的Mixin #
javascript
function createValidationMixin(rules) {
return {
validate: function(attrs) {
var errors = {};
for (var field in rules) {
var fieldRules = rules[field];
fieldRules.forEach(function(rule) {
var value = attrs[field];
if (rule.required && !value) {
errors[field] = rule.message || field + ' is required';
}
if (rule.pattern && value && !rule.pattern.test(value)) {
errors[field] = rule.message || field + ' is invalid';
}
if (rule.minLength && value && value.length < rule.minLength) {
errors[field] = rule.message || field + ' is too short';
}
});
}
return Object.keys(errors).length > 0 ? errors : undefined;
}
};
}
var User = Backbone.Model.extend({});
_.extend(User.prototype, createValidationMixin({
name: [
{ required: true, message: 'Name is required' },
{ minLength: 2, message: 'Name must be at least 2 characters' }
],
email: [
{ required: true, message: 'Email is required' },
{ pattern: /^[\w-]+@[\w-]+\.[a-z]+$/i, message: 'Invalid email' }
]
}));
4.3 组合Mixin #
javascript
var mixin = function(target) {
var sources = Array.prototype.slice.call(arguments, 1);
sources.forEach(function(source) {
var descriptors = {};
Object.keys(source).forEach(function(key) {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
});
Object.defineProperties(target, descriptors);
});
return target;
};
var User = Backbone.Model.extend({});
mixin(User.prototype, TimestampMixin, ValidationMixin, SelectableMixin);
五、实用示例 #
5.1 表单Mixin #
javascript
var FormMixin = {
getFormData: function() {
var data = {};
this.$('input, select, textarea').each(function() {
var $el = $(this);
var name = $el.attr('name');
if (name) {
data[name] = $el.val();
}
});
return data;
},
setFormData: function(data) {
for (var name in data) {
var $el = this.$('[name="' + name + '"]');
if ($el.length) {
$el.val(data[name]);
}
}
},
clearForm: function() {
this.$('input, select, textarea').val('');
},
validateForm: function(rules) {
var data = this.getFormData();
var errors = {};
for (var field in rules) {
var value = data[field];
var fieldRules = rules[field];
if (fieldRules.required && !value) {
errors[field] = fieldRules.requiredMessage || 'Required';
}
}
return Object.keys(errors).length > 0 ? errors : null;
}
};
5.2 拖拽Mixin #
javascript
var DraggableMixin = {
enableDrag: function(options) {
var self = this;
options = options || {};
this.$el.addClass('draggable');
this.$el.on('mousedown.draggable', function(e) {
e.preventDefault();
var startX = e.pageX;
var startY = e.pageY;
var startPos = self.$el.position();
$(document).on('mousemove.draggable', function(e) {
var dx = e.pageX - startX;
var dy = e.pageY - startY;
self.$el.css({
left: startPos.left + dx,
top: startPos.top + dy
});
self.trigger('drag', { dx: dx, dy: dy });
});
$(document).on('mouseup.draggable', function() {
$(document).off('.draggable');
self.trigger('drag:end');
});
self.trigger('drag:start');
});
},
disableDrag: function() {
this.$el.removeClass('draggable');
this.$el.off('.draggable');
}
};
六、总结 #
6.1 Mixin最佳实践 #
| 实践 | 说明 |
|---|---|
| 单一职责 | 每个Mixin只做一件事 |
| 无状态优先 | 避免Mixin内部状态 |
| 命名约定 | 使用Mixin后缀 |
| 文档化 | 说明Mixin的用途和依赖 |
6.2 适用场景 #
- 通用功能复用
- 跨继承链的代码共享
- 功能组合
- 插件式扩展
最后更新:2026-03-28