BackBone 源碼解讀及思考

說明

前段時間略忙,終於找到時間看看backbone代碼。javascript

正如知友們說的那樣,backbone簡單、隨性。 代碼簡單的看一眼,就能知道做者的思路。由於簡單,因此隨性,能夠很自由的和其餘類庫大搭配使用,不太要求特別的格式。html

本文會關注backbone實現的細節,總體框架在博客園的一位朋友已經總結的很好了。連接:點擊這裏java

本文Bakcbone版本:node

    //  定義Backbone版本
    Backbone.VERSION =  ' 0.9.2 ' ;  

   

照例提點問題,有目的的讀源碼。ajax

  1. 怎麼和模板引擎配合使用的。
  2. RESTful JSON 接口從服務器檢索到的數據
  3. 經過源碼,瞭解到平時使用時的哪些須要注意的細節。
  4. 當models中值被改變時自動觸發一個」change」事件、全部用於展現models數據的views都會偵聽到這個事件,而後進行從新渲染。如何實現?
  5. 如何兼容IE6+
  6. view model collection怎麼關聯的。

總體框架

  
   (function() {
        Backbone.Events         //  自定義事件
        Backbone.Model         //  模型構造函數和原型擴展
        Backbone.Collection     //  集合構造函數和原型擴展
        Backbone.Router         //  路由配置器構造函數和原型擴展
        Backbone.History         //  路由器構造函數和原型擴展
        Backbone.View             //  視圖構造函數和原型擴展
        Backbone.sync             //  異步請求工具方法
         var extend = function (protoProps, classProps) { ... }  //  自擴展函數
        Backbone.Model.extend = Backbone.Collection.extend =          Backbone.Router.extend = Backbone.View.extend = extend;  //  自擴展方法
    }).call( this );  

   

 

本文的思路是按照這個框架,提煉每一個部分的重要部分,解決上面提出的問題,並註釋平時使用時須要注意的細節。算法

 

 

Events

  
   var Events = Backbone.Events = {
         //  該方法相似與DOM Level2中的addEventListener方法
        
//  events容許指定多個事件名稱, 經過空白字符進行分隔(如空格, 製表符等)
        
//  當事件名稱爲"all"時, 在調用trigger方法觸發任何事件時, 均會調用"all"事件中綁定的全部回調函數
        on : function(events, callback, context) {
             var calls,  event, node, tail, list;
             if(!callback)
                 return  this;
             //  eventSplitter = /\s+/; 把多個事件名組成的字符串分開。
            events = events.split(eventSplitter);

             //  全部事件都存在this._callbacks這個數組裏面
            calls =  this._callbacks || ( this._callbacks = {});

             whileevent = events.shift()) {
                list = calls[ event];
                node = list ? list.tail : {};
                node.next = tail = {};
                node.context = context;
                node.callback = callback;
                 //  從新組裝當前事件的回調列表, 列表中已經加入了本次回調事件
                calls[ event] = {
                    tail : tail,
                    next : list ? list.next : node
                };
            }
             //  返回當前對象, 方便進行方法鏈調用
             return  this;
        },
         //  - 若是context爲空, 則移除全部的callback指定的函數
        
//  - 若是callback爲空, 則移除事件中全部的回調函數
        
//  - 若是events爲空, 但指定了callback或context, 則移除callback或context指定的回調函數(不區分事件名稱)
        
//  - 若是沒有傳遞任何參數, 則移除對象中綁定的全部事件和回調函數
        off : function(events, callback, context) {
             //  code...
             return  this;
        },
         //  觸發已經定義的一個或多個事件, 依次執行綁定的回調函數列表
        trigger : function(events) {
             var  event, node, calls, tail, args, all, rest;
             //  當前對象沒有綁定任何事件
             if(!( calls =  this._callbacks))
                 return  this;
             //  獲取回調函數列表中綁定的"all"事件列表
            all = calls.all;
             //  將須要觸發的事件名稱, 按照eventSplitter規則解析爲一個數組
            events = events.split(eventSplitter);
             //  將trigger從第2個以後的參數, 記錄到rest變量, 將依次傳遞給回調函數
            rest = slice.call(arguments,  1);

             //  循環須要觸發的事件列表
             whileevent = events.shift()) {
                 //  此處的node變量記錄了當前事件的全部回調函數列表
                 if( node = calls[ event]) {
                    tail = node.tail;
                     //  node變量的值, 按照事件的綁定順序, 被依次賦值爲綁定的單個回調事件對象
                    
//  最後一次綁定的事件next屬性, 與tail引用同一個對象, 以此做爲是否到達列表末尾的判斷依據
                     while(( node = node.next) !== tail) {
                         //  執行全部綁定的事件, 並將調用trigger時的參數傳遞給回調函數
                        node.callback.apply(node.context ||  this, rest);
                    }
                }
                 if( node = all) {
                    tail = node.tail;
                     //  與調用普通事件的回調函數不一樣之處在於, all事件會將當前調用的事件名做爲第一個參數傳遞給回調函數
                    args = [ event].concat(rest);
                     //  遍歷並執行"all"事件中的回調函數列表
                     while(( node = node.next) !== tail) {
                        node.callback.apply(node.context ||  this, args);
                    }
                }
            }

             return  this;
        }
    };
     //  綁定事件與釋放事件的別名, 也爲了同時兼容Backbone之前的版本
    Events.bind = Events.on;
     Events.unbind = Events.off;  

    

Modeljson

Model 比較經常使用,不少細節,因此列出了幾個重要的函數,註釋了一些重要細節。數組

  
   _.extend(Model.prototype, Events, {
          //  code..
        
//  設置模型中的數據, 若是key值不存在, 則做爲新的屬性添加到模型, 若是key值已經存在, 則修改成新的值
         set : function(key, value, options) {
             var attrs, attr, val;

             //  參數形式容許key-value對象形式, 或經過key, value兩個參數進行單獨設置
            
//  若是key是一個對象, 則認定爲使用對象形式設置, 第二個參數將被視爲options參數
             if(_.isObject(key) || key ==  null) {
                attrs = key;
                options = value;
            }  else {
                attrs = {};
                attrs[key] = value;
            }

             //  options配置項必須是一個對象, 若是沒有設置options則默認值爲一個空對象
            options || ( options = {});
             if(!attrs)
                 return  this;
             //  若是被設置的數據對象屬於Model類的一個實例, 則將Model對象的attributes數據對象賦給attrs
            
//  通常在複製一個Model對象的數據到另外一個Model對象時, 會執行該動做
             if( attrs instanceof Model)
                attrs = attrs.attributes;

             //  通常在複製一個Model對象的數據到另外一個Model對象時, 但僅僅須要複製Model中的數據而不須要複製值時執行該操做
             if(options.unset)
                 for(attr  in attrs)
                attrs[attr] =
                 void  0;

             //  若是設置了validate() 函數,則須要驗證
             if(! this._validate(attrs, options))
                 return  false;

             //  若是設置的id屬性名被包含在數據集合中, 則將id覆蓋到模型的id屬性
            
//  這是爲了確保在自定義id屬性名後, 訪問模型的id屬性時, 也能正確訪問到id
             if( this.idAttribute  in attrs)
                 this.id = attrs[ this.idAttribute];

             var changes = options.changes = {};
             //  now記錄當前模型中的數據對象
             var now =  this.attributes;
             //  escaped記錄當前模型中經過escape緩存過的數據
             var escaped =  this._escapedAttributes;
             //  prev記錄模型中數據被改變以前的值
             var prev =  this._previousAttributes || {};

             //  code..

            
//  若是沒有配置silent參數,則須要觸發change函數。
             if(!options.silent)
                 this.change(options);
             return  this;
        },
         //  從服務器獲取默認的模型數據, 獲取數據後使用set方法將數據填充到模型, 所以若是獲取到的數據與當前模型中的數據不一致, 將會觸發change事件
        fetch : function(options) {
            options = options ? _.clone(options) : {};
             var model =  this;
             var success = options.success;
             //  當獲取數據成功後填充數據並調用自定義成功回調函數
            options.success = function(resp, status, xhr) {
                 //  若是填充數據時驗證失敗, 則不會調用自定義success回調函數
                 if(!model. set(model.parse(resp, xhr), options))
                     return  false;
                 //  調用自定義的success回調函數
                 if(success)
                    success(model, resp);
            };
             //  請求發生錯誤時經過wrapError處理error事件
            options.error = Backbone.wrapError(options.error, model, options);
             //  全部的讀取數據(Model, Collection)都是經過sync提供的HTTP方法操做
             return ( this.sync || Backbone.sync).call( this' read 'this, options);
        },
         //  保存模型中的數據到服務器
        save : function(key, value, options) {
             //  attrs存儲須要保存到服務器的數據對象
             var attrs, current;

             //  支持設置單個屬性的方式 key: value
            
//  支持對象形式的批量設置方式 {key: value}
             if(_.isObject(key) || key ==  null) {
                 //  若是key是一個對象, 則認爲是經過對象方式設置
                
//  此時第二個參數被認爲是options
                attrs = key;
                options = value;
            }  else {
                 //  若是是經過key: value形式設置單個屬性, 則直接設置attrs
                attrs = {};
                attrs[key] = value;
            }
             //  配置對象必須是一個新的對象
            options = options ? _.clone(options) : {};

             //  若是在options中設置了wait選項, 則被改變的數據將會被提早驗證, 且服務器沒有響應新數據(或響應失敗)時, 本地數據會被還原爲修改前的狀態
            
//  若是沒有設置wait選項, 則不管服務器是否設置成功, 本地數據均會被修改成最新狀態
             if(options.wait) {
                 //  對須要保存的數據提早進行驗證
                 if(! this._validate(attrs, options))
                     return  false;
                current = _.clone( this.attributes);
            }

             var model =  this;
             //  在options中能夠指定保存數據成功後的自定義回調函數
             var success = options.success;
            options.success = function(resp, status, xhr) {
                 var serverAttrs = model.parse(resp, xhr);
                 if(options.wait) {
                    delete options.wait;
                    serverAttrs = _.extend(attrs || {}, serverAttrs);
                }
                 //  若是調用set方法時驗證失敗, 則不會調用自定義的success回調函數
                 if(!model. set(serverAttrs, options))
                     return  false;
                 if(success) {
                     //  調用響應成功後自定義的success回調函數
                    success(model, resp);
                }  else {
                     //  若是沒有指定自定義回調, 則默認觸發sync事件
                    model.trigger( ' sync ', model, resp, options);
                }
            };
             //  請求發生錯誤時經過wrapError處理error事件
            options.error = Backbone.wrapError(options.error, model, options);

             var method =  this.isNew() ?  ' create ' :  ' update ';
             var xhr = ( this.sync || Backbone.sync).call( this, method,  this, options);
             //  若是設置了options.wait, 則將數據還原爲修改前的狀態
            
//  此時保存的請求尚未獲得響應, 所以若是響應失敗, 模型中將保持修改前的狀態, 若是服務器響應成功, 則會在success中設置模型中的數據爲最新狀態
             if(options.wait)
                 this. set(current, silentOptions);
             return xhr;
        },
         //  code..

    });

Collection

   var Collection = Backbone.Collection = function(models, options) {
        options || ( options = {});
         if(options.model)
             this.model = options.model;
         //  若是設置了comparator屬性, 則集合中的數據將按照comparator方法中的排序算法進行排序(在add方法中會自動調用) 
        
//  固然也能夠服務器作好了傳回來,可是若是先後屬於不一樣團隊就很差作了。
         if(options.comparator)
             this.comparator = options.comparator;

         //  實例化時重置集合的內部狀態(第一次調用時可理解爲定義狀態)
         this._reset();
         this.initialize.apply( this, arguments);

         //  首次調用時設置了silent參數, 所以不會觸發"reset"事件
         if(models)
             this.reset(models, {
                silent :  true,
                parse : options.parse
            });
    };
    _.extend(Collection.prototype, Events, {
         //  定義集合的模型類, 模型類必須是一個Backbone.Model的子類
        model : Model,
        initialize : function() {
        },
         //  返回一個數組, 包含了集合中每一個模型的數據對象
        toJSON : function(options) {
             return  this.map(function(model) {
                 return model.toJSON(options);
            });
        },
         //  默認會觸發"add"事件, 若是在options中設置了silent屬性, 能夠關閉這次事件觸發
        
//  傳入的models能夠是一個或一系列的模型對象(Model類的實例), 若是在集合中設置了model屬性, 則容許直接傳入數據對象(如 {name: 'test'}), 將自動將數據對象實例化爲model指向的模型對象
        add : function(models, options) {
             var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
            options || ( options = {});
             //  models必須是一個數組, 若是隻傳入了一個模型, 則將其轉換爲數組
            models = _.isArray(models) ? models.slice() : [models];

             //  遍歷須要添加的模型列表, 遍歷過程當中, 將執行如下操做:
            
//  - 將數據對象轉化模型對象
            
//  - 創建模型與集合之間的引用
            
//  - 記錄無效和重複的模型, 並在後面進行過濾
             for( i =  0, length = models.length; i < length; i++) {

                 //  當前模型的cid和id
                cid = model.cid;
                id = model.id;
                 //  dups數組中記錄了無效或重複的模型索引(models數組中的索引), 並在下一步進行過濾刪除
                 if(cids[cid] ||  this._byCid[cid] || ((id !=  null) && (ids[id] ||  this._byId[id]))) {
                    dups.push(i);
                     continue;
                }
                cids[cid] = ids[id] = model;
            }
             //  從models中刪除無效或重複的模型, 保留目前集合中真正須要添加的模型列表
            i = dups.length;
             while(i--) {
                models.splice(dups[i],  1);
            }

             //  code ...

            
//  遍歷新增長的模型列表
             for( i =  0, length =  this.models.length; i < length; i++) {
                 if(!cids[( model =  this.models[i]).cid])
                     continue;
                options.index = i;
                 //  觸發模型的"add"事件, 由於集合監聽了模型的"all"事件, 所以在_onModelEvent方法中, 集合也將觸發"add"事件
                
//  詳細信息可參考Collection.prototype._onModelEvent方法
                model.trigger( ' add ', model,  this, options);
            }
             return  this;
        },
         //  若是沒有設置options.silent參數, 將觸發模型的remove事件, 同時將觸發集合的remove事件(集合經過_onModelEvent方法監聽了模型的全部事件)
        remove : function(models, options) {
             var i, l, index, model;
            options || ( options = {});
            models = _.isArray(models) ? models.slice() : [models];
             //  遍歷須要移除的模型列表
             for( i =  0, l = models.length; i < l; i++) {
                model =  this.getByCid(models[i]) ||  this. get(models[i]);
                 if(!model)
                     continue;
                delete  this._byId[model.id];
                delete  this._byCid[model.cid];
                index =  this.indexOf(model);
                 this.models.splice(index,  1);
                 this.length--;
                 //  若是沒有設置silent屬性, 則觸發模型的remove事件
                 if(!options.silent) {
                    options.index = index;
                    model.trigger( ' remove ', model,  this, options);
                }
                 this._removeReference(model);
            }
             return  this;
        },
        push : function(model, options) {
            model =  this._prepareModel(model, options);
             this.add(model, options);
             return model;
        },
         //  code ..
   });

     

Router & History瀏覽器

  
   var Router = Backbone.Router = function(options) {
        options || ( options = {});
         if(options.routes)
             this.routes = options.routes;
         this._bindRoutes();
         this.initialize.apply( this, arguments);
    };
    _.extend(Router.prototype, Events, {
         //  將一個路由規則綁定給一個監聽事件, 當URL片斷匹配該規則時, 會自動調用觸發該事件
        route : function(route, name, callback) {
             //  建立history實例, Backbone.history是一個單例對象, 只在第一次建立路由器對象時被實例化
            Backbone.history || (Backbone.history =  new History);
             //  code ...
            Backbone.history.route(route, _.bind(function(fragment) {
                 var args =  this._extractParameters(route, fragment);
                 //  調用callback路由監聽事件, 並將參數傳遞給監聽事件
                callback && callback.apply( this, args);

                 this.trigger.apply( this, [ ' route: ' + name].concat(args));
                 //  觸發history實例中綁定的route事件, 當路由器匹配到任何規則時, 均會觸發該事件
                Backbone.history.trigger( ' route 'this, name, args);

            },  this));
             return  this;
        },
        //  code ..
    });

     //  History通常不會被直接調用, 在第一次實例化Router對象時, 將自動建立一個History的單例(經過Backbone.history訪問)
     var History = Backbone.History = function() {
         this.handlers = [];
         //  checkUrl方法用於在監聽到URL發生變化時檢查並調用loadUrl方法
        _.bindAll( this' checkUrl ');
    };

    _.extend(History.prototype, Events, {
         //  當用戶使用低版本的IE瀏覽器(不支持onhashchange事件)時, 經過心跳監聽路由狀態的變化
        
//  interval屬性設置心跳頻率(毫秒), 該頻率若是過低可能會致使延遲, 若是過高可能會消耗CPU資源(須要考慮用戶使用低端瀏覽器時的設備配置)
        interval :  50,
         //  獲取location中Hash字符串(錨點#後的片斷)
        getHash : function(windowOverride) {
             //  若是傳入了一個window對象, 則從該對象中獲取, 不然默認從當前window對象中獲取
             var loc = windowOverride ? windowOverride.location : window.location;
             //  將錨點(#)後的字符串提取出來並返回
             var match = loc.href.match(/#(.*)$/);
             return match ? match[ 1] :  '';
        },
         //  根據當前設置的路由方式, 處理並返回當前URL中的路由片斷
        getFragment : function(fragment, forcePushState) {
             //  fragment是經過getHash或從URL中已經提取的待處理路由片斷(如 #/id/1288)
             if(fragment ==  null) {
                 if( this._hasPushState || forcePushState) {
                     //  使用了pushState方式進行路由
                    fragment = window.location.pathname;
                     //  search記錄當前頁面後的參數內容
                     var search = window.location.search;
                     //  將路徑和參數合併在一塊兒, 做爲待處理的路由片斷
                     if(search)
                        fragment += search; 
                }  else {
                     //  使用了hash方式進行路由
                    
//  經過getHash方法獲取當前錨點(#)後的字符串做爲路由片斷
                    fragment =  this.getHash();
                }
            }
             if(!fragment.indexOf( this.options.root))
                fragment = fragment.substr( this.options.root.length);
             //  若是URL片斷首字母爲"#"或"/", 則去除該字符
             return fragment.replace(routeStripper,  '');
        },
         //  該方法做爲整個路由的調度器, 它將針對不一樣瀏覽器監聽URL片斷的變化, 負責驗證並通知到監聽函數
        start : function(options) {

             //  (若是手動設置了options.pushState爲true, 且瀏覽器支持pushState特性, 則會使用pushState方式)
             this._wantsHashChange =  this.options.hashChange !==  false;
             //  _wantsPushState屬性記錄是否但願使用pushState方式來記錄和導航路由器
            
//  pushState是HTML5中爲window.history添加的新特性, 若是沒有手動聲明options.pushState爲true, 則默認將使用hash方式
             this._wantsPushState = !! this.options.pushState;

             //  _hasPushState屬性記錄瀏覽器是否支持pushState特性
             this._hasPushState = !!( this.options.pushState && window.history && window.history.pushState);

             var fragment =  this.getFragment();
             //  documentMode是IE瀏覽器的獨有屬性, 用於標識當前瀏覽器使用的渲染模式
             var docMode = document.documentMode;
             var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <=  7));

             if(oldIE) {
                 //  若是用戶使用低版本的IE瀏覽器, 不支持popstate和onhashchange事件
                
//  向DOM中插入一個隱藏的iframe, 並經過改變和心跳監聽該iframe的URL實現路由
                 this.iframe = $( ' <iframe src="javascript:0" tabindex="-1" /> ').hide().appendTo( ' body ')[ 0].contentWindow;
                 this.navigate(fragment);
            }

             //  開始監聽路由狀態變化
             if( this._hasPushState) {
                 //  若是使用了pushState方式路由, 且瀏覽器支持該特性, 則將popstate事件監聽到checkUrl方法
                $(window).bind( ' popstate 'this.checkUrl);
            }  else  if( this._wantsHashChange && ( ' onhashchange '  in window) && !oldIE) {
                 //  若是使用Hash方式進行路由, 且瀏覽器支持onhashchange事件, 則將hashchange事件監聽到checkUrl方法
                $(window).bind( ' hashchange 'this.checkUrl);
            }  else  if( this._wantsHashChange) {
                 //  對於低版本的瀏覽器, 經過setInterval方法心跳監聽checkUrl方法, interval屬性標識心跳頻率
                 this._checkUrlInterval = setInterval( this.checkUrl,  this.interval);
            }
             //  code ..
        },
         //  中止history對路由的監控, 並將狀態恢復爲未監聽狀態
        stop : function() {
             //  解除對瀏覽器路由的onpopstate和onhashchange事件的監聽
            $(window).unbind( ' popstate 'this.checkUrl).unbind( ' hashchange 'this.checkUrl);
             //  中止對於低版本的IE瀏覽器的心跳監控
            clearInterval( this._checkUrlInterval);
             //  恢復started狀態, 便於下次從新調用start方法
            History.started =  false;
        },
         //  該方法在onpopstate和onhashchange事件被觸發後自動調用, 或者在低版本的IE瀏覽器中由setInterval心跳定時調用
        checkUrl : function(e) {
             //  獲取當前的URL片斷
             var current =  this.getFragment();
             //  對低版本的IE瀏覽器, 將從iframe中獲取最新的URL片斷並賦給current變量
             if(current ==  this.fragment &&  this.iframe)
                current =  this.getFragment( this.getHash( this.iframe));
             //  若是當前URL與上一次的狀態沒有發生任何變化, 則中止執行
             if(current ==  this.fragment)
                 return  false;

             //  執行到這裏, URL已經發生改變, 調用navigate方法將URL設置爲當前URL
             if( this.iframe)
                 this.navigate(current);
             //  調用loadUrl方法, 檢查匹配的規則, 並執行規則綁定的方法
             this.loadUrl() ||  this.loadUrl( this.getHash());
        },
         //  code ..

    });

 

View緩存

  
   //  Backbone.View 視圖相關
     var View = Backbone.View = function(options) {
         //  爲每個視圖對象建立一個惟一標識, 前綴爲"view"
         this.cid = _.uniqueId( ' view ');
         this._configure(options || {});
         this._ensureElement();
         this.initialize.apply( this, arguments);
         this.delegateEvents();
    };

     //  viewOptions列表記錄一些列屬性名, 在構造視圖對象時, 若是傳遞的配置項中包含這些名稱, 則將屬性複製到對象自己
     var viewOptions = [ ' model '' collection '' el '' id '' attributes '' className '' tagName '];
    _.extend(View.prototype, Events, {
         //  若是在建立視圖對象時, 沒有設置指定的el元素, 則會經過make方法建立一個元素, tagName爲建立元素的默認標籤
        tagName :  ' div ',
         //  code ..
        
//  移除當前視圖的$el元素
        remove : function() {
             //  經過調用jQuery或Zepto的remove方法, 所以在第三方庫中會同時移除該元素綁定的全部事件和數據
             this.$el.remove();
             return  this;
        },
         //  該方法用於在內部建立this.el時自動調用
        make : function(tagName, attributes, content) {
             var el = document.createElement(tagName);
             if(attributes)
                $(el).attr(attributes);
             if(content)
                $(el).html(content);
             return el;
        },
         //  爲視圖對象設置標準的$el及el屬性, 該方法在對象建立時被自動調用
        setElement : function(element,  delegate) {
             //  this.$el 存放Jquery或其餘庫的示例對象
             this.$el = ( element instanceof $) ? element : $(element);
             //  this.el存放標準的DOM對象
             this.el =  this.$el[ 0];
             //  code ...
             return  this;
        },
         //  爲視圖元素綁定事件
        
//  events參數配置了須要綁定事件的集合, 格式如('事件名稱 元素選擇表達式' : '事件方法名稱/或事件函數'):
        
//  {
        
//      'click #title': 'edit',
        
//      'click .save': 'save'
        
//      'click span': function() {}
        
//  }
        
//  該方法在視圖對象初始化時會被自動調用, 並將對象中的events屬性做爲events參數(事件集合)
        delegateEvents : function(events) {
             if(!(events || ( events = getValue( this' events '))))
                 return;
             //  取消當前已經綁定過的events事件
             this.undelegateEvents();
             for( var key  in events) {
                 //  code ...                
                
//  解析事件表達式(key), 從表達式中解析出事件的名字和須要操做的元素
                
//  例如 'click #title'將被解析爲 'click' 和 '#title' 兩部分, 均存放在match數組中
                 var match = key.match(delegateEventSplitter);
                 //  eventName爲解析後的事件名稱
                
//  selector爲解析後的事件元素選擇器表達式
                 var eventName = match[ 1], selector = match[ 2];

                method = _.bind(method,  this);
                 //  設置事件名稱, 在事件名稱後追加標識, 用於傳遞給jQuery或Zepto的事件綁定方法
                eventName +=  ' .delegateEvents ' +  this.cid;
                 if(selector ===  '') {
                     this.$el.bind(eventName, method);
                }  else {
                     this.$el. delegate(selector, eventName, method);
                }
            }
        },

         //  在實例化視圖對象時設置初始配置
        
//  將傳遞的配置覆蓋到對象的options中
        
//  將配置中與viewOptions列表相同的配置複製到對象自己, 做爲對象的屬性
        _configure : function(options) {
             //  若是對象自己設置了默認配置, 則使用傳遞的配置進行合併
             if( this.options)
                options = _.extend({},  this.options, options);
             //  遍歷viewOptions列表
             for( var i =  0, l = viewOptions.length; i < l; i++) {
                 //  attr依次爲viewOptions中的屬性名
                 var attr = viewOptions[i];
                 //  將options配置中與viewOptions相同的配置複製到對象自己, 做爲對象的屬性
                 if(options[attr])
                     this[attr] = options[attr];
            }
             this.options = options;
        },
        _ensureElement : function() {

             if(! this.el) {
                 //  若是沒有設置el屬性, 則建立默認元素
                 var attrs = getValue( this' attributes ') || {};
                 if( this.id)
                    attrs.id =  this.id;
                 if( this.className)
                    attrs[ ' class '] =  this.className;
                 //  經過make方法建立元素, 並調用setElement方法將元素設置爲視圖所使用的標準元素
                 this.setElement( this.make( this.tagName, attrs),  false);
            }  else {
                 //  若是設置了el屬性, 則直接調用setElement方法將el元素設置爲視圖的標準元素
                 this.setElement( this.el,  false);
            }
        }
   });

   

 

Backbone.sync

 

var  methodMap = {
         ' create ' :  ' POST ',
         ' update ' :  ' PUT ',
         ' delete ' :  ' DELETE ',
         ' read ' :  ' GET '
    };
     //  Async用於在Backbone中操做數據時, 向服務器發送請求同步數據狀態, 以創建與服務器之間的鏈接
    
//  sync發送默認經過第三方庫(jQuery, Zepto等) $.ajax方法發送請求, 所以若是要調用狀態同步相關的方法, 須要第三方庫支持
    
//  Model Collection save 或者fetch都用這個這個類。
    Backbone.sync = function(method, model, options) {
         //  根據CRUD方法名定義與服務器交互的方法(POST, GET, PUT, DELETE)
         var type = methodMap[method];

         //  params將做爲請求參數對象傳遞給第三方庫的$.ajax方法
         var  params = {
             //  請求類型
            type : type,
             //  數據格式默認爲json
            dataType :  ' json '
        };

         //  若是在發送請求時沒有在options中設置url地址, 將會經過模型對象的url屬性或方法來獲取url
         if(!options.url) {
             params.url = getValue(model,  ' url ') || urlError();
        }

         if(!options.data && model && (method ==  ' create ' || method ==  ' update ')) {
             params.contentType =  ' application/json ';
             params.data = JSON.stringify(model.toJSON());
        }
         if(Backbone.emulateHTTP) {
             //  若是操做類型爲PUT或DELETE
             if(type ===  ' PUT ' || type ===  ' DELETE ') {
                 //  將操做名稱存放到_method參數發送到服務器
                 if(Backbone.emulateJSON)
                     params.data._method = type;
                 //  實際以POST方式進行提交, 併發送X-HTTP-Method-Override頭信息
                 params.type =  ' POST ';
                 params.beforeSend = function(xhr) {
                    xhr.setRequestHeader( ' X-HTTP-Method-Override ', type);
                };
            }
        }
         //  經過第三方庫的$.ajax方法向服務器發送請求同步數據狀態
         return $.ajax(_.extend( params, options));

    };

 

使用

 

  
   var extend = function(protoProps, classProps) {
         //  child存儲已經實現繼承自當前類的子類(Function)
        
//  protoProps設置子類原型鏈中的屬性
        
//  classProps設置子類的靜態屬性
         var child = inherits( this, protoProps, classProps);
         //  將extend函數添加到子類, 所以調用子類的extend方法即可實現對子類的繼承
        child.extend =  this.extend;
         //  返回實現繼承的子類
         return child;
    };
     //  爲Model, Collection, Router和View類實現繼承機制 每次使用只須要 Backbone.View.extend({...});
    Model.extend = Collection.extend = Router.extend = View.extend = extend;  

   

說明

本次分析基本上對翻譯源碼註釋,中間省略了一些我的認爲對理解代碼實現和平時應用關係不大的代碼。

相關文章
相關標籤/搜索