Backbone.js视图事件委托 #

一、事件委托基础 #

1.1 什么是事件委托 #

事件委托是利用事件冒泡机制,在父元素上统一处理子元素的事件。

text
传统方式:每个子元素绑定事件处理器
事件委托:在父元素上统一处理,通过target判断来源

优势:
├── 减少事件处理器数量
├── 动态元素自动支持
└── 内存占用更低

1.2 events对象 #

Backbone.js 使用 events 对象声明式绑定事件:

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn-edit': 'editUser',
        'click .btn-delete': 'deleteUser',
        'click': 'select'
    },
    
    editUser: function(e) {
        console.log('编辑用户');
    },
    
    deleteUser: function(e) {
        console.log('删除用户');
    },
    
    select: function(e) {
        console.log('选中用户');
    }
});

1.3 事件格式 #

text
'事件名 选择器': '处理函数名'
部分 说明 示例
事件名 DOM事件类型 click, keypress, submit
选择器 子元素选择器 .btn, #submit, input
处理函数 方法名或函数 ‘onClick’, function() {}

二、事件绑定 #

2.1 基本绑定 #

javascript
var TodoView = Backbone.View.extend({
    events: {
        'click': 'toggle',
        'click .delete': 'clear'
    },
    
    toggle: function() {
        this.model.toggle();
    },
    
    clear: function(e) {
        e.stopPropagation();
        this.model.destroy();
    }
});

2.2 绑定多个事件 #

javascript
var FormView = Backbone.View.extend({
    events: {
        'click .btn-save': 'save',
        'click .btn-cancel': 'cancel',
        'keypress input': 'onKeyPress',
        'submit': 'onSubmit'
    }
});

2.3 同一处理函数 #

javascript
var InputView = Backbone.View.extend({
    events: {
        'keypress input': 'update',
        'change input': 'update',
        'input input': 'update'
    },
    
    update: function(e) {
        var $target = $(e.target);
        this.model.set($target.attr('name'), $target.val());
    }
});

2.4 直接绑定函数 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': function(e) {
            console.log('按钮被点击');
        }
    }
});

2.5 函数形式events #

javascript
var UserView = Backbone.View.extend({
    events: function() {
        var events = {
            'click .btn-edit': 'edit'
        };
        
        if (this.options.deletable) {
            events['click .btn-delete'] = 'delete';
        }
        
        return events;
    }
});

三、事件类型 #

3.1 鼠标事件 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click': 'onClick',
        'dblclick': 'onDoubleClick',
        'mouseenter': 'onMouseEnter',
        'mouseleave': 'onMouseLeave',
        'mousedown': 'onMouseDown',
        'mouseup': 'onMouseUp'
    },
    
    onClick: function(e) {
        console.log('单击');
    },
    
    onDoubleClick: function(e) {
        console.log('双击');
    },
    
    onMouseEnter: function(e) {
        this.$el.addClass('hover');
    },
    
    onMouseLeave: function(e) {
        this.$el.removeClass('hover');
    }
});

3.2 键盘事件 #

javascript
var InputView = Backbone.View.extend({
    events: {
        'keypress': 'onKeyPress',
        'keydown': 'onKeyDown',
        'keyup': 'onKeyUp'
    },
    
    onKeyPress: function(e) {
        if (e.which === 13) {
            this.submit();
        }
    },
    
    onKeyDown: function(e) {
        if (e.which === 27) {
            this.cancel();
        }
    }
});

3.3 表单事件 #

javascript
var FormView = Backbone.View.extend({
    events: {
        'submit': 'onSubmit',
        'change input': 'onChange',
        'input input': 'onInput',
        'focus input': 'onFocus',
        'blur input': 'onBlur'
    },
    
    onSubmit: function(e) {
        e.preventDefault();
        this.save();
    },
    
    onChange: function(e) {
        var $input = $(e.target);
        this.model.set($input.attr('name'), $input.val());
    },
    
    onInput: function(e) {
        this.validate($(e.target));
    },
    
    onFocus: function(e) {
        $(e.target).addClass('focused');
    },
    
    onBlur: function(e) {
        $(e.target).removeClass('focused');
    }
});

3.4 其他事件 #

javascript
var View = Backbone.View.extend({
    events: {
        'scroll': 'onScroll',
        'resize': 'onResize',
        'contextmenu': 'onContextMenu'
    }
});

四、事件对象 #

4.1 事件参数 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': 'onClick'
    },
    
    onClick: function(e) {
        console.log('事件类型:', e.type);
        console.log('目标元素:', e.target);
        console.log('当前元素:', e.currentTarget);
        console.log('鼠标位置:', e.pageX, e.pageY);
        console.log('按键码:', e.which);
    }
});

4.2 阻止默认行为 #

javascript
var LinkView = Backbone.View.extend({
    events: {
        'click a': 'onClick'
    },
    
    onClick: function(e) {
        e.preventDefault();
        console.log('链接被点击,但不会跳转');
    }
});

4.3 阻止冒泡 #

javascript
var TodoView = Backbone.View.extend({
    events: {
        'click': 'toggle',
        'click .delete': 'delete'
    },
    
    toggle: function() {
        console.log('切换完成状态');
    },
    
    delete: function(e) {
        e.stopPropagation();
        console.log('删除任务');
    }
});

4.4 获取数据 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .item': 'select'
    },
    
    render: function() {
        this.$el.html(
            '<div class="item" data-id="1">项目1</div>' +
            '<div class="item" data-id="2">项目2</div>'
        );
        return this;
    },
    
    select: function(e) {
        var id = $(e.currentTarget).data('id');
        console.log('选中:', id);
    }
});

五、动态事件绑定 #

5.1 delegateEvents #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': 'onClick'
    },
    
    addDynamicEvent: function() {
        this.events['click .new-btn'] = 'onNewClick';
        this.delegateEvents();
    },
    
    removeEvent: function() {
        delete this.events['click .btn'];
        this.delegateEvents();
    }
});

5.2 undelegateEvents #

javascript
var UserView = Backbone.View.extend({
    disableEvents: function() {
        this.undelegateEvents();
    },
    
    enableEvents: function() {
        this.delegateEvents();
    }
});

5.3 临时禁用事件 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': 'onClick'
    },
    
    performAction: function() {
        this.undelegateEvents();
        
        this.doSomething();
        
        this.delegateEvents();
    }
});

六、事件上下文 #

6.1 this指向 #

事件处理函数中的 this 指向视图实例:

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': 'onClick'
    },
    
    onClick: function(e) {
        console.log(this === view);
        console.log(this.model.get('name'));
    }
});

6.2 保持上下文 #

javascript
var UserView = Backbone.View.extend({
    events: {
        'click .btn': function(e) {
            this.onClick(e);
        }.bind(this)
    },
    
    onClick: function(e) {
        console.log(this.model.get('name'));
    }
});

七、实用示例 #

7.1 可编辑视图 #

javascript
var EditableView = Backbone.View.extend({
    events: {
        'dblclick .title': 'startEdit',
        'blur .edit-input': 'endEdit',
        'keypress .edit-input': 'saveOnEnter'
    },
    
    template: _.template(
        '<span class="title"><%= title %></span>'
    ),
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },
    
    startEdit: function(e) {
        var $title = $(e.currentTarget);
        var value = $title.text();
        
        $title.hide();
        this.$el.append(
            '<input class="edit-input" value="' + value + '">'
        );
        
        this.$('.edit-input').focus();
    },
    
    endEdit: function(e) {
        var $input = $(e.currentTarget);
        var value = $input.val().trim();
        
        if (value) {
            this.model.set('title', value);
        }
        
        $input.remove();
        this.$('.title').show();
    },
    
    saveOnEnter: function(e) {
        if (e.which === 13) {
            $(e.currentTarget).blur();
        }
    }
});

7.2 拖拽视图 #

javascript
var DraggableView = Backbone.View.extend({
    events: {
        'mousedown': 'startDrag',
        'touchstart': 'startDrag'
    },
    
    initialize: function() {
        this.dragging = false;
        $(document).on('mousemove.draggable', this.onDrag.bind(this));
        $(document).on('mouseup.draggable', this.endDrag.bind(this));
    },
    
    startDrag: function(e) {
        e.preventDefault();
        this.dragging = true;
        this.startX = e.pageX || e.originalEvent.touches[0].pageX;
        this.startY = e.pageY || e.originalEvent.touches[0].pageY;
        this.startLeft = this.$el.position().left;
        this.startTop = this.$el.position().top;
    },
    
    onDrag: function(e) {
        if (!this.dragging) return;
        
        var pageX = e.pageX || e.originalEvent.touches[0].pageX;
        var pageY = e.pageY || e.originalEvent.touches[0].pageY;
        
        var left = this.startLeft + (pageX - this.startX);
        var top = this.startTop + (pageY - this.startY);
        
        this.$el.css({ left: left, top: top });
    },
    
    endDrag: function() {
        this.dragging = false;
    },
    
    remove: function() {
        $(document).off('.draggable');
        Backbone.View.prototype.remove.call(this);
    }
});

7.3 表单验证视图 #

javascript
var FormValidationView = Backbone.View.extend({
    events: {
        'submit': 'onSubmit',
        'input input': 'validateField',
        'blur input': 'validateField'
    },
    
    validateField: function(e) {
        var $input = $(e.currentTarget);
        var name = $input.attr('name');
        var value = $input.val();
        var error = this.validate(name, value);
        
        if (error) {
            this.showError($input, error);
        } else {
            this.clearError($input);
        }
    },
    
    validate: function(name, value) {
        var rules = {
            name: function(v) {
                if (!v) return '姓名不能为空';
                if (v.length < 2) return '姓名至少2个字符';
            },
            email: function(v) {
                if (!v) return '邮箱不能为空';
                if (!v.match(/^[\w-]+@[\w-]+\.[a-z]+$/i)) return '邮箱格式不正确';
            }
        };
        
        return rules[name] && rules[name](value);
    },
    
    showError: function($input, error) {
        $input.addClass('error');
        $input.siblings('.error-message').text(error).show();
    },
    
    clearError: function($input) {
        $input.removeClass('error');
        $input.siblings('.error-message').hide();
    },
    
    onSubmit: function(e) {
        e.preventDefault();
        
        var hasError = false;
        var self = this;
        
        this.$('input').each(function() {
            var $input = $(this);
            var error = self.validate($input.attr('name'), $input.val());
            
            if (error) {
                self.showError($input, error);
                hasError = true;
            }
        });
        
        if (!hasError) {
            this.save();
        }
    }
});

八、总结 #

8.1 事件方法 #

方法 说明
delegateEvents() 绑定events中定义的事件
undelegateEvents() 解绑所有事件
$(selector) 在视图内查找元素

8.2 常用事件 #

事件 说明
click 单击
dblclick 双击
keypress 按键
submit 表单提交
change 值变化
focus/blur 聚焦/失焦

8.3 最佳实践 #

  1. 使用 events 对象声明事件
  2. 事件处理函数中 this 指向视图
  3. 使用 e.stopPropagation() 阻止冒泡
  4. 使用 e.preventDefault() 阻止默认行为
  5. 及时解绑不再需要的事件
最后更新:2026-03-28