第一个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应用,我们学习了:

  1. Model:定义数据结构和业务逻辑
  2. Collection:管理多个模型实例
  3. View:渲染UI和处理用户交互
  4. Events:组件间的通信机制
  5. 数据持久化:使用localStorage保存数据

这个应用展示了Backbone.js的核心概念和基本用法,为后续深入学习打下基础。

最后更新:2026-03-28