Backbone.js视图渲染 #

一、渲染基础 #

1.1 render方法 #

render 是视图的核心方法,负责将模型数据渲染为HTML:

javascript
var UserView = Backbone.View.extend({
    render: function() {
        this.$el.html('<h1>' + this.model.get('name') + '</h1>');
        return this;
    }
});

1.2 返回this #

render 方法应该返回 this 以支持链式调用:

javascript
var view = new UserView({ model: user });
$('#app').html(view.render().el);

1.3 渲染时机 #

javascript
var UserView = Backbone.View.extend({
    initialize: function() {
        this.listenTo(this.model, 'change', this.render);
    },
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

二、模板引擎 #

2.1 Underscore模板 #

Backbone.js 默认使用 Underscore.js 的模板引擎:

javascript
var UserView = Backbone.View.extend({
    template: _.template(
        '<div class="user">' +
        '  <h2><%= name %></h2>' +
        '  <p><%= email %></p>' +
        '</div>'
    ),
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

2.2 模板语法 #

语法 说明
<%= %> 输出转义后的值
<%- %> 输出HTML转义后的值
<% %> 执行JavaScript代码

2.3 模板变量 #

javascript
var template = _.template(
    '<% if (name) { %>' +
    '  <h2><%= name %></h2>' +
    '<% } else { %>' +
    '  <h2>匿名用户</h2>' +
    '<% } %>'
);

var html = template({ name: '张三' });

2.4 循环渲染 #

javascript
var template = _.template(
    '<ul>' +
    '<% items.forEach(function(item) { %>' +
    '  <li><%= item.name %></li>' +
    '<% }); %>' +
    '</ul>'
);

var html = template({
    items: [
        { name: '项目1' },
        { name: '项目2' }
    ]
});

2.5 外部模板 #

html
<script type="text/template" id="user-template">
    <div class="user">
        <h2><%= name %></h2>
        <p><%= email %></p>
    </div>
</script>
javascript
var UserView = Backbone.View.extend({
    template: _.template($('#user-template').html()),
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

2.6 Handlebars模板 #

javascript
var UserView = Backbone.View.extend({
    template: Handlebars.compile(
        '<div class="user">' +
        '  <h2>{{name}}</h2>' +
        '  <p>{{email}}</p>' +
        '</div>'
    ),
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

三、渲染策略 #

3.1 完整渲染 #

每次重新渲染整个视图:

javascript
var UserView = Backbone.View.extend({
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

3.2 局部渲染 #

只更新变化的部分:

javascript
var UserView = Backbone.View.extend({
    initialize: function() {
        this.listenTo(this.model, 'change:name', this.updateName);
        this.listenTo(this.model, 'change:email', this.updateEmail);
    },
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },
    
    updateName: function(model, value) {
        this.$('.name').text(value);
    },
    
    updateEmail: function(model, value) {
        this.$('.email').text(value);
    }
});

3.3 条件渲染 #

javascript
var UserView = Backbone.View.extend({
    render: function() {
        var data = this.model.toJSON();
        
        this.$el.html(this.template({
            name: data.name,
            email: data.email,
            isAdmin: data.role === 'admin',
            lastLogin: this.formatDate(data.lastLogin)
        }));
        
        return this;
    },
    
    formatDate: function(date) {
        return new Date(date).toLocaleDateString();
    }
});

3.4 延迟渲染 #

javascript
var UserView = Backbone.View.extend({
    initialize: function() {
        this.listenTo(this.model, 'change', this.debouncedRender);
    },
    
    debouncedRender: _.debounce(function() {
        this.render();
    }, 100),
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

四、集合渲染 #

4.1 渲染列表 #

javascript
var UserListView = Backbone.View.extend({
    tagName: 'ul',
    
    render: function() {
        this.$el.empty();
        
        this.collection.each(function(user) {
            var userView = new UserView({ model: user });
            this.$el.append(userView.render().el);
        }, this);
        
        return this;
    }
});

4.2 子视图管理 #

javascript
var UserListView = Backbone.View.extend({
    initialize: function() {
        this.childViews = [];
        this.listenTo(this.collection, 'add', this.addOne);
        this.listenTo(this.collection, 'remove', this.removeOne);
        this.listenTo(this.collection, 'reset', this.render);
    },
    
    render: function() {
        this.clearChildViews();
        this.collection.each(this.addOne, this);
        return this;
    },
    
    addOne: function(user) {
        var view = new UserView({ model: user });
        this.childViews.push(view);
        this.$el.append(view.render().el);
    },
    
    removeOne: function(user) {
        var view = _.find(this.childViews, function(v) {
            return v.model === user;
        });
        
        if (view) {
            view.remove();
            this.childViews = _.without(this.childViews, view);
        }
    },
    
    clearChildViews: function() {
        this.childViews.forEach(function(view) {
            view.remove();
        });
        this.childViews = [];
    },
    
    remove: function() {
        this.clearChildViews();
        Backbone.View.prototype.remove.call(this);
    }
});

4.3 增量渲染 #

javascript
var UserListView = Backbone.View.extend({
    initialize: function() {
        this.listenTo(this.collection, 'add', this.addOne);
        this.listenTo(this.collection, 'remove', this.removeOne);
    },
    
    addOne: function(user) {
        var view = new UserView({ model: user });
        this.$el.append(view.render().el);
    },
    
    removeOne: function(user) {
        this.$('[data-id="' + user.id + '"]').remove();
    }
});

五、性能优化 #

5.1 文档片段 #

javascript
var UserListView = Backbone.View.extend({
    render: function() {
        var fragment = document.createDocumentFragment();
        
        this.collection.each(function(user) {
            var view = new UserView({ model: user });
            fragment.appendChild(view.render().el);
        });
        
        this.$el.empty().append(fragment);
        return this;
    }
});

5.2 缓存DOM引用 #

javascript
var UserView = Backbone.View.extend({
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        
        this.$name = this.$('.name');
        this.$email = this.$('.email');
        
        return this;
    },
    
    updateName: function(name) {
        this.$name.text(name);
    },
    
    updateEmail: function(email) {
        this.$email.text(email);
    }
});

5.3 批量更新 #

javascript
var UserListView = Backbone.View.extend({
    initialize: function() {
        this.pendingUpdate = false;
        this.listenTo(this.collection, 'change', this.scheduleUpdate);
    },
    
    scheduleUpdate: function() {
        if (!this.pendingUpdate) {
            this.pendingUpdate = true;
            requestAnimationFrame(this.render.bind(this));
        }
    },
    
    render: function() {
        this.pendingUpdate = false;
        this.$el.html(this.template({
            users: this.collection.toJSON()
        }));
        return this;
    }
});

5.4 虚拟滚动 #

javascript
var VirtualListView = Backbone.View.extend({
    initialize: function(options) {
        this.itemHeight = options.itemHeight || 50;
        this.visibleCount = Math.ceil(this.$el.height() / this.itemHeight) + 2;
        this.startIndex = 0;
        
        this.$scrollContainer = options.scrollContainer || $(window);
        this.$scrollContainer.on('scroll', this.onScroll.bind(this));
    },
    
    onScroll: function() {
        var scrollTop = this.$scrollContainer.scrollTop();
        var newStartIndex = Math.floor(scrollTop / this.itemHeight);
        
        if (newStartIndex !== this.startIndex) {
            this.startIndex = newStartIndex;
            this.render();
        }
    },
    
    render: function() {
        var endIndex = Math.min(
            this.startIndex + this.visibleCount,
            this.collection.length
        );
        
        var fragment = document.createDocumentFragment();
        
        for (var i = this.startIndex; i < endIndex; i++) {
            var model = this.collection.at(i);
            var view = new ItemView({ model: model });
            fragment.appendChild(view.render().el);
        }
        
        this.$el.empty().append(fragment);
        this.$el.css('padding-top', this.startIndex * this.itemHeight);
        
        return this;
    }
});

六、实用示例 #

6.1 可复用渲染器 #

javascript
var Renderer = {
    render: function(template, data) {
        return template(data);
    },
    
    renderCollection: function(collection, itemView, container) {
        var fragment = document.createDocumentFragment();
        
        collection.each(function(model) {
            var view = new itemView({ model: model });
            fragment.appendChild(view.render().el);
        });
        
        container.empty().append(fragment);
    }
};

var UserListView = Backbone.View.extend({
    render: function() {
        Renderer.renderCollection(this.collection, UserView, this.$el);
        return this;
    }
});

6.2 条件渲染 #

javascript
var ConditionalView = Backbone.View.extend({
    render: function() {
        if (this.model.isNew()) {
            this.renderNew();
        } else if (this.model.hasChanged()) {
            this.renderChanged();
        } else {
            this.renderNormal();
        }
        return this;
    },
    
    renderNew: function() {
        this.$el.html(this.newTemplate(this.model.toJSON()));
    },
    
    renderChanged: function() {
        this.$el.html(this.changedTemplate(this.model.toJSON()));
    },
    
    renderNormal: function() {
        this.$el.html(this.template(this.model.toJSON()));
    }
});

6.3 异步模板加载 #

javascript
var AsyncTemplateView = Backbone.View.extend({
    initialize: function() {
        this.templateLoaded = false;
        this.loadTemplate();
    },
    
    loadTemplate: function() {
        var self = this;
        $.get('/templates/user.html').done(function(html) {
            self.template = _.template(html);
            self.templateLoaded = true;
            self.render();
        });
    },
    
    render: function() {
        if (this.templateLoaded) {
            this.$el.html(this.template(this.model.toJSON()));
        } else {
            this.$el.html('<div class="loading">加载中...</div>');
        }
        return this;
    }
});

七、总结 #

7.1 渲染方法 #

方法 说明
html() 替换内容
append() 追加内容
prepend() 前置内容
empty() 清空内容

7.2 模板引擎 #

引擎 特点
Underscore 内置,简单
Handlebars 功能强大,预编译
Mustache 轻量,多语言支持
EJS 类似JSP语法

7.3 最佳实践 #

  1. 使用模板引擎分离HTML
  2. 返回 this 支持链式调用
  3. 使用文档片段优化性能
  4. 缓存DOM引用
  5. 合理使用增量渲染
最后更新:2026-03-28