Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Backbone 开发实战

edokeh
September 01, 2013

Backbone 开发实战

edokeh

September 01, 2013
Tweet

More Decks by edokeh

Other Decks in Technology

Transcript

  1. Backbone 原理 Model & Collection View DOM 服务器 事件通知 change

    add, remove … 更新 DOM 用户交互事件 调用方法 更新数据
  2. Backbone 的哲学 关注点分离 简洁、灵活 • 依赖 Underscore 与 jQuery,丏业的事交给丏业的库 •

    基本的数据模型支持,但没有针对复杂场景建模 • View 非常简单,由开发者决定如何渲染 DOM Model • 数据存取 • 事件通知 • 业务逻辑 View • 根据 Model 展示界面 • 监听事件,修改界面 • 将用户的输入反馈给 Model Backbone 给开发者预留的空间太大 反而让不少初学者感到迷惑
  3. 以数据为中心 events : { 'click li' : 'changeStatus' }, changeStatus

    : function (e) { var target = $(e.currentTarget); var status = target.data('status'); this.$('.title').text(target.text()); this.model.set('status', status); }, initialize : function() { this.listenTo(this.model, 'change', this.render); } 不要将数据与展示混在一起 请抛弃 jQuery 思维
  4. Model 的关联 var Group = Backbone.Model.extend({ initialize : function (options)

    { // 直接使用属性保存,Friends 是 Collection this.friends = new Friends(options.friends); // 做一下属性的清理 this.unset('friends'); } }); var group = new Group({ name : '我的好友', friends : [ {name : 'geliang'}, {name : 'zhangxu'} ] }); Group 包含多个 Friend 但 Backbone 没有内置 Model 的关联 • 简单的场景下选用简单的方式 • 复杂的场景可以尝试Backbone-relational 等 • 可以将 friends 的事件冒泡至自身 this.listenTo(this.friends, 'all', function () { arguments[0] = 'friends:' + arguments[0]; this.trigger.apply(this, arguments); });
  5. 虚属性 initialize : function (options) { …… this.listenTo(this.friends, 'add remove

    change:status', this.updateCount); }, updateCount : function () { this.set({ 'onlineCount' : ……, 'totalCount' : this.friends.length }); } 虚属性 • 添加虚属性可以方便 View 的渲染 • 面向场景不同,因此客户端与服务器的模型不必完全一致 • 请让服务器接口宽松一些 服务器并不保存 Group 的在线人数
  6. View 的渲染效率 var UserView = Backbone.View.extend({ template : _.template($('#tpl').html()), initialize

    : function () { this.listenTo(this.model, 'change', this.render); this.render(); }, render : function () { var html = this.template(this.model.toJSON()); this.$el.html(html); } }); 修改一个属性就重绘整个 View 会不会有效率问题?
  7. View 的渲染效率 initialize : function () { this.listenTo(this.model, 'change:name', this.renderName);

    this.render(); }, renderName:function(){ this.$('.name').text(this.model.get('name')); }, render : function () { var html = this.template(this.model.toJSON()); this.$el.html(html); this.renderName(); } 注意避免渲染逻辑的重复 PS:Backbone.ModelBinder 等方案可以简化这个过程
  8. View 的渲染效率 局部渲染 vs 整体渲染 • 性能有差距,尤其是 IE 较明显 •

    从绝对值来看,整体渲染的性能可以接受 • 模板变大时,整体渲染性能下降 73 984 35 151 0 200 400 600 800 1000 1200 Chrome IE8 改变 status 属性 1000 次 局部渲染 整体渲染 1525 152 984 151 0 500 1000 1500 2000 整体渲染 局部渲染 大模板 vs 小模板 小模板 大模板 <a class="{{ status }}">{{ nick }}</a>
  9. View 的渲染效率 局部渲染 vs 整体渲染 • 尽量使用整体渲染,减少复杂度 • 针对明显的性能瓶颈使用局部渲染 •

    细粒度的 View • 选用更快的模板引擎(?) 99 224 173 2886 1771 0 500 1000 1500 2000 2500 3000 3500 artTemplate Handlebars doT underscore Mustache 100 条数据万次渲染
  10. 渲染 Collection 问题场景 • 用 Collection 存储分组的好友列表 • 每个好友对应一个的 View

    • 该怎么编写这个分组的 View? FriendView 展示 Friend 模型 GroupView 对应 Group 模型 Group 包含 Friend 的 Collection
  11. 渲染 Collection 需要考虑的问题 • 编写逻辑添加、删除子 View • 内部维护所有子 View 以便查找

    • 管理子 View 的生命周期 Marionette.View ItemView CollectionView Layout CompositeView Backbone.View 建议使用 Marionette 的 View 组件
  12. 渲染 Collection 试试 ItemView var FriendView = Backbone.Marionette.ItemView.extend({ template :

    _.template($('#item-template').html()), initialize : function () { this.render(); this.model.on('change', this.render, this); }, render : function () { this.$el.html(this.template(this.model.toJSON())); } }); template : '#item-template', modelEvents : { 'change' : 'render' }, ui : { 'status' : '.status' }, onRender : function () { } 指定模板时可以用选择器 默认的 render 方法 有 events 为啥没有 modelEvents 呢? 可以这样用 this.ui.status.addClass(…) 渲染完毕乊后的回调
  13. 渲染 Collection 超简单的 CollectionView var FriendsView = Backbone.Marionette.CollectionView.extend({ itemView: FriendView,

    emptyView: EmptyFriendView }); var friendsView = new FriendsView({ collecion : friends }); • 自劢监听并处理 collection 的 add, remove, reset 事件 • 子 view 的生命周期的自劢管理 • 不支持模板,完全由子 View 组成 与 FriendView 配合渲染 friends 为空时显示这个 View 显示 Group 可以用 CompositeView
  14. 渲染 Collection 更强大的 CompositeView var GroupView = Backbone.Marionette.CompositeView.extend({ template :

    '#group-tpl' itemView : FriendView, emptyView : EmptyFriendView, itemViewContainer : 'ul', }); var groupView = new GroupView({ model : group, collection : group.friends }); CollectionView & CompositeView • 回调函数:onAfterItemAdded, onItemRemoved • 子 View 事件冒泡至自身:itemview:* • 重载 appendHtml 自定义子 View 如何放置 • …… 模板,同 ItemView 指定子 view 该添加到哪里
  15. 如何处理超大 View 使用 Marionette.Layout var ChatWindowView = Backbone.Marionette.Layout.extend({ template :

    '#chat-window-tpl', regions : { 'titleWrap' : '#titleWrap', 'companyWrap' : '#companyWrap' }, onRender : function () { this.titleWrap.show(new TitleView({model : this.model})); this.companyWrap.show(new CompanyView({model : this.model})); } }); 模板,同 ItemView 定义 Region 渲染完毕后组装子 View 通过 Region 的 show 方法显示子 View • Region 可以自劢管理子 View 的生命周期 • .close() 可以彻底删除子 View • .show() 会隐式调用 .close() • 尽量避免 Layout 的整体重绘
  16. View 之间如何交互 EventBus • 一个对所有对象可见的“事件频道” • Backbone 对象自带这项功能 var FaceView

    = Backbone.Marionette.ItemView.extend({ events : { 'click .face' : 'selectFace' }, selectFace : function () { Backbone.trigger('selectFace', $(this).data('value')); } }); var InputView = Backbone.View.extend({ initialize : function () { Backbone.on('selectFace', this.insertText, this); }, // ... });
  17. View 之间如何交互 中介者模式 • 由中介者监听事件,并作出处理 • 推荐由父 view 充当中介者 var

    FaceView = Backbone.Marionette.ItemView.extend({ selectFace : function () { this.trigger('selectFace', $(this).data('value')); } }); var MainView = Backbone.Marionette.Layout.extend({ onRender : function () { var inputView = new InputView(); var faceView = new FaceView(); faceView.on('selectFace', inputView.insertText); // ... } });
  18. 如何启动应用 使用 Marionette.Application var app = new Backbone.Marionette.Application(); app.addRegions({ 'main'

    : '#main', 'nav' : '#nav' }); app.addInitializer(function () { this.main.show(new MainView()); }); app.addInitializer(function () { this.nav.show(new NavView()); }); app.start(); 又见 Region 定义多个 Initializer 以拆分代码 吭劢应用,会执行 Initializer
  19. mixin var Status = { isOnline : function () {

    // ... }, statusText : function () { // ... } }; _.extend(Friend.prototype, Status); _.extend(User.prototype, Status); • 通过 mixin 在多个类乊间复用代码 • 可以看做是包含了实现的 Java Interface • 规避了继承和组合
  20. 慎用 _.bindAll(this) var Friends = Backbone.Collection.extend({ model : Friend, initialize

    : function () { _.bindAll(this); } // ... }); var friends = new Friends([{name : 'geliang'}]); 通过 bindAll 希望确保所有方法 执行时 this 依然为 Friends 对象 IE 下会报错 • _.bindAll(this) 是对所有 function 属性执行 _.bind() • _.bind() 为 IE 提供的实现对 new 操作符支持有问题 • Underscore 1.5 已经修复,但 bindAll 不再支持单个参数 • 养成好习惯,请明确指定需要 bind 的参数 Friend 也被 bind 了 Marionette.CollectionView 等尤其要注意哦! _.bindAll(this, 'methodA', 'methodB');
  21. 使用 .ListenTo() var FriendView = Backbone.View.extend({ initialize : function ()

    { this.model.on('change', this.render, this); this.listenTo(this.model, 'change', this.render); }, // ... }); 传入此参数指定回调函数的 this • listenTo 不需要额外参数来指定 this • Marionette.View 的 modelEvents 等属性调用了 listenTo • listenTo 让监听者能够保存所有事件监听,方便后续移除 remove: function() { this.$el.remove(); this.stopListening(); return this; }