【原创】backbone1.1.0源码解析之View

囧,文章昨儿就发布了,结果移出了首页。

好吧,我再详细说一下吧~~

 

作为MVC框架,M(odel)  V(iew)  C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图)

通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们好讨厌在逻辑代码里面参杂dom的代码,特别是需要生产dom的代码,

因为这样的缺点:

1. 耦合,难于维护(虽然模版引擎能解决一些问题,但是事件的添加呢?)

2. 代码无法做到美观,节俭,感觉和dom参杂在一起就是一个字,乱!!

 

介于这样的缺点,Backbone提供了一个View类,用于构造对象,它可以做到一下几点:

1. 帮你创建dom,你只要负责去渲染这个dom就行了(我们用模板引擎就行dom的渲染就很合理)

2. 帮你为dom添加(代理)事件,所以无需自己去手动绑定事件啦,而只需要提供events的参数。

 

这样下来,上面的两个缺点基本都解决了~~

 

View(视图)

视图对象用来封装前端的dom元素,也就是说跟dom元素关联在一起,视图的改变即是对应dom元素的改变。

因此我们在创建视图类或者在构造视图对象的时候,我们需要传递dom元素,我们可以这样:

1. 构造视图对象时,传递el参数,可以是jquery对象,也可以是dom对象(前提是该dom元素在页面里面已存在)

1
2
3
var bookView = new Backbone.View({
    el : $(‘#id‘)    // jquery对象,或dom对象
});

 而接下来我们要做的就只是往这个dom元素里添加内容了,像这样:

1
2
3
4
5
6
7
initialize : function () {
     
    var tpl =‘xxx‘;
    var data = {...};
 
    this.$el.html(_.template(tpl, data));
}

这里我们更建议你去使用_.template这样的的模板函数就渲染dom,会显得代码更节俭,美观

 

2. 创建视图类时,传递tagName(默认是div),让View来帮你创建dom元素

1
2
3
4
5
6
7
8
9
10
11
var BookView = Backbone.View.extend({
 
    tagName : ‘span‘
});
var bookView = new BookView({
    attributes : {
        ‘title‘ : ‘xxx‘  // span的标签的属性
    },
 
    id : ‘demo‘
});

记住最后render时要将这个元素append到页面里面去,因为这里的dom元素是通过tagName,Backbone在内部创建的,并未添加到文档流中去。

 

说到dom元素,当然就有事件,向click,hover等事件是必不可少的,有了事件才有了交互,有了交互才会有数据(model)的变化才会有dom(view)的更新。

所以接下来要说一下如何给已有dom添加事件

1. 在创建视图类的时候,可以通过events参数添加事件,像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var BookView = Backbone.View.extend({
 
    tagName : ‘span‘
 
    events : {
 
        ‘mouseenter‘ : function () {...},  // 事件,某一函数(this对象指向view实例)
 
        ‘click a.item‘ : ‘say‘  // 事件代理,为class为item的a标签代理,调用view对象的say方法
 
    },
 
    say : function () {
        console.log(this);
    }
});

 

注意:

     (1)Backbone的事件都是添加在视图关联的那个dom元素(el)上,那么该元素就有可能成为其子元素的事件代理元素(冒泡原理)

     (2)事件的回调函数中的this是该视图对象

     (3)‘click a.item‘ 这里是一个事件代理,

     (4)回调函数如果不是函数,是字符串,那么会到view对象中获取对应的方法,想这里的say其实是viewObj.say

 

2. 当然我们也可以通过调用delegateEvents方法添加事件,像这样:

1
2
3
4
5
6
7
8
var bookView = new BookView(...);
 
bookView.delegateEvents({
 
    ‘click‘ : function () {...},
 
    ‘click a.item‘ : ‘say‘
});

但是这里要注意的是:每次调用这个方法,前面添加的事件会被先清除

 

Backbone是如何更新视图的呢?

很明显,视图还会关联一个model实例,通过监听model实例的变化,从而相应的更新视图

这里是一个简单的小例子:

首先,我们在initialize函数中,要求view实例监听model实例的change事件

然后,在回调函数中将model,change后的状态表现出来,这里是model的price属性变化了

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var BookModel = Backbone.Model.extend();
 
var BookView = Backbone.View.extend({
 
    tagName : ‘span‘,
 
    initialize : function () {
 
        this.listenTo(this.model, ‘change‘, this._change);
       this.$el.html(this.model.get(‘price‘));
        $(‘body‘).append(this.$el);
    },
 
    _change : function (model) {
       
        this.$el.html(‘更新:‘ + model.get(‘price‘));
    }
});
 
 
var bookModel = new BookModel({
    price : 10
});
 
var bookView = new BookView({
 
    model : bookModel,
 
    attributes : {
        ‘title‘ : ‘span‘
    },
 
    id : ‘demo‘
});
 
setTimeout(function () {
    bookModel.set(‘price‘, 30);
}, 1000);

 

例子在这:demo 

 

 当然,这里只是简单演示一下,其实,一般最后的bookModel.set(‘price‘, 30),都是用户通过交互来做到的,也就是说这段代码应该在

dom的事件函数中表现出来,于是我们就可以为dom添加对应的事件了

 

ok,接下来把源码解析的中文注释附上,如果错误了,还望指出来,谢谢~~

 

【原创】backbone1.1.0源码解析之View
  1   // Backbone.View
  2   // -------------
  3 
  4   // Backbone Views are almost more convention than they are actual code. A View
  5   // is simply a JavaScript object that represents a logical chunk of UI in the
  6   // DOM. This might be a single item, an entire list, a sidebar or panel, or
  7   // even the surrounding frame which wraps your whole app. Defining a chunk of
  8   // UI as a **View** allows you to define your DOM events declaratively, without
  9   // having to worry about render order ... and makes it easy for the view to
 10   // react to specific changes in the state of your models.
 11 
 12   // Creating a Backbone.View creates its initial element outside of the DOM,
 13   // if an existing element is not provided...
 14   var View = Backbone.View = function(options) {
 15 
 16     // 实例唯一id
 17     this.cid = _.uniqueId(‘view‘);
 18 
 19     options || (options = {});
 20 
 21     // 从options里挑出存在于viewOptions里的有意义的属性值,
 22     // 并赋值到this里对象里,作为属性
 23     _.extend(this, _.pick(options, viewOptions));
 24 
 25     // view是跟dom有关的
 26     // 所以这里要先确定dom元素是否指定
 27     this._ensureElement();
 28 
 29     // 初始化,一般用户来自定义
 30     this.initialize.apply(this, arguments);
 31 
 32     // 添加(代理)事件
 33     this.delegateEvents();
 34   };
 35 
 36   // Cached regex to split keys for `delegate`.
 37   // 在添加事件时,分开事件名和选择器
 38   var delegateEventSplitter = /^(\S+)\s*(.*)$/;
 39 
 40   // List of view options to be merged as properties.
 41   // 将可能作为view实例的一些属性名(由用户传入)
 42   var viewOptions = [‘model‘, ‘collection‘, ‘el‘, ‘id‘, ‘attributes‘, ‘className‘, ‘tagName‘, ‘events‘];
 43 
 44   // Set up all inheritable **Backbone.View** properties and methods.
 45   // 原型方法
 46   _.extend(View.prototype, Events, {
 47 
 48     // The default `tagName` of a View‘s element is `"div"`.
 49     // 用于构造dom元素,默认是div元素i,用户可以传递参数覆盖
 50     tagName: ‘div‘,
 51 
 52     // jQuery delegate for element lookup, scoped to DOM elements within the
 53     // current view. This should be preferred to global lookups where possible.
 54     // 将对元素的查找缩小到与试图关联的dom元素($el)内,提高效率
 55     $: function(selector) {
 56       return this.$el.find(selector);
 57     },
 58 
 59     // Initialize is an empty function by default. Override it with your own
 60     // initialization logic.
 61     // 初始化函数,一般用户自定义覆盖
 62     initialize: function(){},
 63 
 64     // **render** is the core function that your view should override, in order
 65     // to populate its element (`this.el`), with the appropriate HTML. The
 66     // convention is for **render** to always return `this`.
 67     // 渲染函数,用来填充视图关联的dom元素($el)
 68     // 用户自定义覆盖之
 69     // 注意:建议使用模板引擎对处理html,例如已有的_.template()
 70     render: function() {
 71       return this;
 72     },
 73 
 74     // Remove this view by taking the element out of the DOM, and removing any
 75     // applicable Backbone.Events listeners.
 76     // 将视图关联的dom元素删除
 77     // 停止该视图对象的所有的事件监听(一般view视图会监听model的变化,从而相应地更新视图)
 78     remove: function() {
 79       this.$el.remove();
 80       this.stopListening();
 81       return this;
 82     },
 83 
 84     // Change the view‘s element (`this.el` property), including event
 85     // re-delegation.
 86     // 设置与视图关联的dom元素($el)
 87     setElement: function(element, delegate) {
 88 
 89       // 删除之前的$el的事件监听
 90       if (this.$el) this.undelegateEvents();
 91 
 92       // dom对象还是jquery对象的判断,统一转换成jquery对象,赋值给$el
 93       this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
 94 
 95       // 将dom对象赋值给el
 96       this.el = this.$el[0];
 97 
 98       // 是否添加事件
 99       if (delegate !== false) this.delegateEvents();
100       return this;
101     },
102 
103     // Set callbacks, where `this.events` is a hash of
104     //
105     // *{"event selector": "callback"}*
106     //
107     //     {
108     //       ‘mousedown .title‘:  ‘edit‘,
109     //       ‘click .button‘:     ‘save‘,
110     //       ‘click .open‘:       function(e) { ... }
111     //     }
112     //
113     // pairs. Callbacks will be bound to the view, with `this` set properly.
114     // Uses event delegation for efficiency.
115     // Omitting the selector binds the event to `this.el`.
116     // This only works for delegate-able events: not `focus`, `blur`, and
117     // not `change`, `submit`, and `reset` in Internet Explorer.
118     // 给视图关联的dom元素添加事件或者是给它的子元素添加代理事件
119     delegateEvents: function(events) {
120 
121       // 参数events为空,则用this.events代替
122       if (!(events || (events = _.result(this, ‘events‘)))) return this;
123 
124       // 在调用时,会先去除之前的所有的事件监听
125       this.undelegateEvents();
126 
127       // 遍历events对象
128       for (var key in events) {
129 
130         // method为方法名(字符串)或者函数
131         var method = events[key];
132 
133         // 如果不是函数(即是字符串),那么表示调用的是view对象对应名字的方法
134         if (!_.isFunction(method)) method = this[events[key]];
135 
136         // 如果方法为空,跳过
137         if (!method) continue;
138 
139         // 匹配出事件名(eventName)和选择器(selector)
140         var match = key.match(delegateEventSplitter);
141         var eventName = match[1], selector = match[2];
142 
143         // 将方法的this对象指定为该视图对象
144         method = _.bind(method, this);
145 
146         // 修改事件名,添加命名空间,便于以后事件的删除
147         // .delegateEvents用于区分其他程序在同一个dom元素上绑定的事件
148         // cid用于区分不同的视图对象共享同一个dom元素
149         eventName += ‘.delegateEvents‘ + this.cid;
150 
151         // 如果选择器为空,为视图关联的这个dom元素绑定事件
152         if (selector === ‘‘) {
153           this.$el.on(eventName, method);
154 
155         // 如果选择器不为空,则为dom元素下匹配selector的子元素绑定代理事件
156         } else {
157           this.$el.on(eventName, selector, method);
158         }
159       }
160       return this;
161     },
162 
163     // Clears all callbacks previously bound to the view with `delegateEvents`.
164     // You usually don‘t need to use this, but may wish to if you have multiple
165     // Backbone views attached to the same DOM element.
166     // 删除绑定的所有(代理)事件
167     undelegateEvents: function() {
168       this.$el.off(‘.delegateEvents‘ + this.cid);
169       return this;
170     },
171 
172     // Ensure that the View has a DOM element to render into.
173     // If `this.el` is a string, pass it through `$()`, take the first
174     // matching element, and re-assign it to `el`. Otherwise, create
175     // an element from the `id`, `className` and `tagName` properties.
176     // 确保视图有关联的dom元素
177     // 如果没有,试图通过tagName构造
178     _ensureElement: function() {
179 
180       // 如果构造时没有传递了el参数
181       // 那么,用tagName参数构造dom元素,并为该dom元素添加一系列的属性(参数传进来来的)
182       if (!this.el) {
183 
184         // 将要添加到dom元素上的属性列表
185         // 注意:attributes可以是函数,这样可以通过条件判断返回不同的属性
186         // 下面的id,className,tagName,el同样是这样,都通过调用_.result
187         var attrs = _.extend({}, _.result(this, ‘attributes‘));
188 
189         // dom元素id
190         if (this.id) attrs.id = _.result(this, ‘id‘);
191 
192         // dom元素class
193         if (this.className) attrs[‘class‘] = _.result(this, ‘className‘);
194 
195         // 生成dom元素
196         var $el = Backbone.$(‘<‘ + _.result(this, ‘tagName‘) + ‘>‘).attr(attrs);
197 
198         // 设置dom元素
199         this.setElement($el, false);
200       } else {
201 
202         // 设置dom元素
203         this.setElement(_.result(this, ‘el‘), false);
204       }
205     }
206 
207   });
【原创】backbone1.1.0源码解析之View

【原创】backbone1.1.0源码解析之View

上一篇:手把手教你实现三种绑定方式(call、apply、bind)


下一篇:十一、常用的NSArray和NSMutableArray方法