Slide 1

Slide 1 text

Backbone 开发实战 葛亮@焦点科技 2013.09.01

Slide 2

Slide 2 text

麦通 Web 版

Slide 3

Slide 3 text

讲些什么 • Backbone 简介 • Model 篇 • View 篇 • 其他经验

Slide 4

Slide 4 text

Backbone 简介

Slide 5

Slide 5 text

Backbone 原理 Model & Collection View DOM 服务器 事件通知 change add, remove … 更新 DOM 用户交互事件 调用方法 更新数据

Slide 6

Slide 6 text

Backbone 的哲学 关注点分离 简洁、灵活 • 依赖 Underscore 与 jQuery,丏业的事交给丏业的库 • 基本的数据模型支持,但没有针对复杂场景建模 • View 非常简单,由开发者决定如何渲染 DOM Model • 数据存取 • 事件通知 • 业务逻辑 View • 根据 Model 展示界面 • 监听事件,修改界面 • 将用户的输入反馈给 Model Backbone 给开发者预留的空间太大 反而让不少初学者感到迷惑

Slide 7

Slide 7 text

Model 篇 • 以数据为中心 • Model 的关联 • 虚属性

Slide 8

Slide 8 text

以数据为中心 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 思维

Slide 9

Slide 9 text

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); });

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

View 篇 • View 的渲染效率 • 渲染 Collection • 如何处理超大 View • View 乊间如何交互

Slide 12

Slide 12 text

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 会不会有效率问题?

Slide 13

Slide 13 text

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 等方案可以简化这个过程

Slide 14

Slide 14 text

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 小模板 小模板 大模板 {{ nick }}

Slide 15

Slide 15 text

View 的渲染效率 局部渲染 vs 整体渲染 • 尽量使用整体渲染,减少复杂度 • 针对明显的性能瓶颈使用局部渲染 • 细粒度的 View • 选用更快的模板引擎(?) 99 224 173 2886 1771 0 500 1000 1500 2000 2500 3000 3500 artTemplate Handlebars doT underscore Mustache 100 条数据万次渲染

Slide 16

Slide 16 text

渲染 Collection 问题场景 • 用 Collection 存储分组的好友列表 • 每个好友对应一个的 View • 该怎么编写这个分组的 View? FriendView 展示 Friend 模型 GroupView 对应 Group 模型 Group 包含 Friend 的 Collection

Slide 17

Slide 17 text

渲染 Collection 需要考虑的问题 • 编写逻辑添加、删除子 View • 内部维护所有子 View 以便查找 • 管理子 View 的生命周期 Marionette.View ItemView CollectionView Layout CompositeView Backbone.View 建议使用 Marionette 的 View 组件

Slide 18

Slide 18 text

渲染 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(…) 渲染完毕乊后的回调

Slide 19

Slide 19 text

渲染 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

Slide 20

Slide 20 text

渲染 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 该添加到哪里

Slide 21

Slide 21 text

如何处理超大 View 聊天信息区域 公司信息区域 窗口标题 表情选择弹窗 输入区域 快捷键选择菜单 • 拆分成多个子 View • 在父 View 中组装管理这些子 View

Slide 22

Slide 22 text

如何处理超大 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 的整体重绘

Slide 23

Slide 23 text

View 之间如何交互 选择表情乊后,在输入框中显示 • FaceView • InputView

Slide 24

Slide 24 text

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); }, // ... });

Slide 25

Slide 25 text

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); // ... } });

Slide 26

Slide 26 text

View 之间如何交互 该如何选择? • 业务耦合较紧密时选用中介者模式,否则选用 EventBus • 使用中介者模式需要对架构做一些设计 • EventBus 可能会造成一些可维护性困难 • 注意避免 EventBus 的滥用,例如替代 Model 事件

Slide 27

Slide 27 text

其他经验 • 如何吭劢应用 • mixin • 慎用 _.bindAll(this) • 使用 .listenTo()

Slide 28

Slide 28 text

如何启动应用 使用 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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

慎用 _.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');

Slide 31

Slide 31 text

使用 .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; }

Slide 32

Slide 32 text

一些小结 • Backbone 提供了基本的架构,留给开发者较大的自由 • 一件事可能有多种解决方案,根据场景权衡 • Marionette 为一些场景提供了默认解决方案 • Marionette 有大量组件和理念可供挖掘

Slide 33

Slide 33 text

感谢聆听 Questions ?