zoukankan      html  css  js  c++  java
  • 【原创】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元素在页面里面已存在)

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

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

    initialize : function () {
        
        var tpl ='xxx';
        var data = {...};
    
        this.$el.html(_.template(tpl, data));
    }
    

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

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

    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参数添加事件,像这样:

    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方法添加事件,像这样:

    var bookView = new BookView(...);
    
    bookView.delegateEvents({
    
        'click' : function () {...},
    
        'click a.item' : 'say'
    });
    

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

     

    Backbone是如何更新视图的呢?

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

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

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

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

    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,接下来把源码解析的中文注释附上,如果错误了,还望指出来,谢谢~~

      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   });
  • 相关阅读:
    监控服务器配置(一)-----Prometheus安装配置
    mongo可视化工具adminMongo安装
    Grafana 下载与安装(v5.4.1)
    Grafana+Prometheus系统监控之Redis
    聊聊redis的监控工具
    Linux 服务器buff/cache清理
    redis为什么内存不宜过大
    Python 操作 mongodb 亿级数据量使用 Bloomfilter 高效率判断唯一性 例子
    Redis-3.2.0集群配置(redis cluster)
    在reshard过程中,将会询问reshard多少slots:
  • 原文地址:https://www.cnblogs.com/lovesueee/p/3515777.html
Copyright © 2011-2022 走看看