第一个Backbone.js应用 #
一、应用概述 #
我们将创建一个简单的待办事项(Todo)应用,包含以下功能:
- 添加待办事项
- 标记完成状态
- 删除待办事项
- 显示待办事项列表
二、完整代码 #
2.1 HTML结构 #
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Backbone.js Todo App</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.input-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.input-section input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.input-section button {
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.input-section button:hover {
background: #45a049;
}
.todo-list {
list-style: none;
}
.todo-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #eee;
gap: 10px;
}
.todo-item:hover {
background: #f9f9f9;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #999;
}
.todo-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.todo-text {
flex: 1;
font-size: 16px;
}
.delete-btn {
padding: 5px 10px;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.delete-btn:hover {
background: #da190b;
}
.stats {
margin-top: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
text-align: center;
color: #666;
}
</style>
</head>
<body>
<h1>待办事项</h1>
<div class="input-section">
<input type="text" id="todo-input" placeholder="输入待办事项...">
<button id="add-btn">添加</button>
</div>
<ul class="todo-list" id="todo-list"></ul>
<div class="stats" id="stats"></div>
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.6/underscore-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/backbone@1.6.0/backbone-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script>
// 应用代码将在下面添加
</script>
</body>
</html>
2.2 Model(模型) #
javascript
// Todo模型
var Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false,
createdAt: null
},
initialize: function() {
if (!this.get('createdAt')) {
this.set('createdAt', new Date().toISOString());
}
},
toggle: function() {
this.set('completed', !this.get('completed'));
}
});
2.3 Collection(集合) #
javascript
// Todo集合
var TodoList = Backbone.Collection.extend({
model: Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
completed: function() {
return this.filter(function(todo) {
return todo.get('completed');
});
},
remaining: function() {
return this.filter(function(todo) {
return !todo.get('completed');
});
}
});
// 创建集合实例
var todos = new TodoList();
2.4 View(视图) #
javascript
// 单个Todo视图
var TodoView = Backbone.View.extend({
tagName: 'li',
className: 'todo-item',
template: _.template(
'<input type="checkbox" <%= completed ? "checked" : "" %>>' +
'<span class="todo-text"><%= title %></span>' +
'<button class="delete-btn">删除</button>'
),
events: {
'click input[type="checkbox"]': 'toggleCompleted',
'click .delete-btn': 'clear'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('completed', this.model.get('completed'));
return this;
},
toggleCompleted: function() {
this.model.toggle();
},
clear: function() {
this.model.destroy();
}
});
// 应用主视图
var AppView = Backbone.View.extend({
el: 'body',
events: {
'click #add-btn': 'createTodo',
'keypress #todo-input': 'createOnEnter'
},
initialize: function() {
this.input = this.$('#todo-input');
this.list = this.$('#todo-list');
this.stats = this.$('#stats');
this.listenTo(todos, 'add', this.addOne);
this.listenTo(todos, 'reset', this.addAll);
this.listenTo(todos, 'all', this.render);
todos.fetch();
},
render: function() {
var remaining = todos.remaining().length;
var completed = todos.completed().length;
var total = todos.length;
this.stats.html(
'总计: ' + total + ' | ' +
'待完成: ' + remaining + ' | ' +
'已完成: ' + completed
);
},
addOne: function(todo) {
var view = new TodoView({ model: todo });
this.list.append(view.render().el);
},
addAll: function() {
this.list.empty();
todos.each(this.addOne, this);
},
createTodo: function() {
var title = this.input.val().trim();
if (!title) return;
todos.create({
title: title,
completed: false
});
this.input.val('');
},
createOnEnter: function(e) {
if (e.which === 13) {
this.createTodo();
}
}
});
// 启动应用
var app = new AppView();
三、代码解析 #
3.1 Model解析 #
javascript
var Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false,
createdAt: null
},
initialize: function() {
// 初始化时设置创建时间
if (!this.get('createdAt')) {
this.set('createdAt', new Date().toISOString());
}
},
toggle: function() {
// 切换完成状态
this.set('completed', !this.get('completed'));
}
});
要点说明:
| 属性/方法 | 说明 |
|---|---|
| defaults | 定义模型默认属性 |
| initialize | 构造函数,创建实例时调用 |
| toggle | 自定义方法,切换完成状态 |
3.2 Collection解析 #
javascript
var TodoList = Backbone.Collection.extend({
model: Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
completed: function() {
return this.filter(function(todo) {
return todo.get('completed');
});
},
remaining: function() {
return this.filter(function(todo) {
return !todo.get('completed');
});
}
});
要点说明:
| 属性/方法 | 说明 |
|---|---|
| model | 指定集合中模型的类型 |
| localStorage | 使用本地存储持久化数据 |
| completed | 自定义方法,获取已完成的任务 |
| remaining | 自定义方法,获取未完成的任务 |
3.3 View解析 #
javascript
var TodoView = Backbone.View.extend({
tagName: 'li',
className: 'todo-item',
template: _.template(
'<input type="checkbox" <%= completed ? "checked" : "" %>>' +
'<span class="todo-text"><%= title %></span>' +
'<button class="delete-btn">删除</button>'
),
events: {
'click input[type="checkbox"]': 'toggleCompleted',
'click .delete-btn': 'clear'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('completed', this.model.get('completed'));
return this;
}
});
要点说明:
| 属性/方法 | 说明 |
|---|---|
| tagName | 视图的根元素标签 |
| className | 根元素的CSS类名 |
| template | Underscore模板函数 |
| events | DOM事件映射 |
| initialize | 初始化,绑定模型事件 |
| render | 渲染视图 |
四、数据流分析 #
4.1 添加Todo流程 #
text
用户输入 → 点击添加按钮
↓
AppView.createTodo()
↓
todos.create() → 创建Model并保存
↓
触发 'add' 事件
↓
AppView.addOne() → 创建TodoView
↓
TodoView.render() → 渲染DOM
4.2 切换状态流程 #
text
用户点击复选框
↓
TodoView.toggleCompleted()
↓
model.toggle() → 更新模型
↓
触发 'change' 事件
↓
TodoView.render() → 更新DOM
4.3 删除Todo流程 #
text
用户点击删除按钮
↓
TodoView.clear()
↓
model.destroy() → 删除模型
↓
触发 'destroy' 事件
↓
TodoView.remove() → 移除DOM
五、事件绑定详解 #
5.1 DOM事件 #
javascript
events: {
'click #add-btn': 'createTodo',
'keypress #todo-input': 'createOnEnter'
}
格式:'事件名 选择器': '处理函数'
5.2 模型事件 #
javascript
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
}
常用模型事件:
| 事件 | 触发时机 |
|---|---|
| change | 属性变化时 |
| change:attr | 特定属性变化时 |
| destroy | 模型销毁时 |
| sync | 同步完成时 |
| invalid | 验证失败时 |
5.3 集合事件 #
javascript
initialize: function() {
this.listenTo(todos, 'add', this.addOne);
this.listenTo(todos, 'reset', this.addAll);
this.listenTo(todos, 'all', this.render);
}
常用集合事件:
| 事件 | 触发时机 |
|---|---|
| add | 添加模型时 |
| remove | 移除模型时 |
| reset | 重置集合时 |
| sort | 排序时 |
| all | 所有事件 |
六、本地存储 #
6.1 安装localStorage适配器 #
html
<script src="https://cdn.jsdelivr.net/npm/backbone.localstorage@2.0.2/backbone.localStorage-min.js"></script>
6.2 使用localStorage #
javascript
var TodoList = Backbone.Collection.extend({
model: Todo,
localStorage: new Backbone.LocalStorage('todos-backbone')
});
6.3 数据持久化方法 #
javascript
// 创建并保存
todos.create({ title: 'New Todo' });
// 获取所有数据
todos.fetch();
// 保存单个模型
todo.save();
// 删除模型
todo.destroy();
七、扩展功能 #
7.1 添加编辑功能 #
javascript
var TodoView = Backbone.View.extend({
events: {
'dblclick .todo-text': 'edit',
'keypress .edit-input': 'updateOnEnter',
'blur .edit-input': 'close'
},
edit: function() {
this.$el.addClass('editing');
this.$('.todo-text').hide();
this.$el.append('<input class="edit-input" value="' + this.model.get('title') + '">');
this.$('.edit-input').focus();
},
close: function() {
var value = this.$('.edit-input').val().trim();
if (value) {
this.model.set('title', value);
this.model.save();
}
this.$el.removeClass('editing');
this.$('.edit-input').remove();
this.$('.todo-text').show();
},
updateOnEnter: function(e) {
if (e.which === 13) {
this.close();
}
}
});
7.2 添加过滤功能 #
javascript
var AppView = Backbone.View.extend({
events: {
'click .filter-all': 'filterAll',
'click .filter-active': 'filterActive',
'click .filter-completed': 'filterCompleted'
},
filterAll: function() {
this.showAll();
},
filterActive: function() {
this.showRemaining();
},
filterCompleted: function() {
this.showCompleted();
},
showAll: function() {
todos.each(function(todo) {
todo.trigger('visible');
});
},
showRemaining: function() {
todos.each(function(todo) {
todo.trigger('visible', !todo.get('completed'));
});
},
showCompleted: function() {
todos.each(function(todo) {
todo.trigger('visible', todo.get('completed'));
});
}
});
八、总结 #
通过这个简单的Todo应用,我们学习了:
- Model:定义数据结构和业务逻辑
- Collection:管理多个模型实例
- View:渲染UI和处理用户交互
- Events:组件间的通信机制
- 数据持久化:使用localStorage保存数据
这个应用展示了Backbone.js的核心概念和基本用法,为后续深入学习打下基础。
最后更新:2026-03-28