backbone.js源碼解析:extend、Backbone.View


backbone版本:0.9.2

 

1.解析Backbone.Model(Collection | Router | View).extend

 

(1).找到extend的定義 

  // 定義extend函數
   var extend =  function (protoProps, classProps) {
     /*
    通常我們以Backbone.XXX.extend的方式建立一個Model、View等對象類型,所以在此處this表示Backbone.Model、Backbone.View等對應構造函數(注:后面的類似概述統一以Backbone.View為具體例子)
    
*/
     var child = inherits( this, protoProps, classProps);
    child.extend =  this.extend;
     return child;
  };
   // 將Model、Collection、Router、View的extend方法設置為extend
   Model.extend = Collection.extend = Router.extend = View.extend = extend;   

 (2)分析inherits函數源碼

var ctor =  function(){};
     var inherits =  function(parent, protoProps, staticProps) {
     var child;
     // 判斷protoProps是否一個原型對象(prototype),如果是則將child賦值為原型對象所屬的構造函數
     if (protoProps && protoProps.hasOwnProperty('constructor')) {
      child = protoProps.constructor;
    }  else {
       // 否則將新建一個構造函數賦值給child
      child =  function(){ 
         // inherits函數返回的是一個構造函數,我們會用new child()來調用此構造函數(例如:AppView = Backbone.View.extend({});var appView=new AppView();),所以此處的this指向我們new的實例(例如var appView=new AppView(),則this指向appView)
     // new AppView進行的操作其實是Backbone.Model.apply(this,arguments) ,也就是說我們實例化appView的時候其實是調用Backbone.Model
    parent.apply( this, arguments); 
      };
    }

     // 此處parent既 1  中的 this,也就是Backbone.View,_extend是underscore.js里的一個函數,作用是將第二個及第二個以后的所有參數的所有屬性和屬性值設置到第一個參數上(_extend的具體實現在此不贅述,可看underscore.js的源碼)
    _.extend(child, parent);

     // ctor是一個內容為空的構造函數,此處將其原型對象設置為Backbone.View.prototype
    ctor.prototype = parent.prototype;
     // 將child的原型對象設置為一個ctor的實例,child.prototype.contructor指向ctor
    child.prototype =  new ctor();
     // 將Backbone.View.extend的第二個參數(一般是一個對象)的的所有屬性復制到child.prototype
     if (protoProps) _.extend(child.prototype, protoProps);

     // 將Backbone.View.extend的第三個參數(一般是一個對象)的的所有屬性復制到child,也就是給child設置靜態屬性或方法
     if (staticProps) _.extend(child, staticProps);
    
     // 執行完child.prototype=new ctor后,child.prototype.constructor已經不指向child,所以此處需要顯示設置
    child.prototype.constructor = child;

     // EcmaScript中並沒有定義__super__這個屬性,此處應該是backbone記錄child對應的super類
    child.__super__ = parent.prototype;
    
     return child;
  };  

(3)總結出原型對象鏈

new 自定義View等() 所屬類--> child(用戶創造的構造函數)  原型對象--> ctor的一個實例(我們自定義的一些函數和方法都設置到此實例上)  原型對象--> Backbon.View.prototype

 

 

 

2.解析Backbone.View
例子:

AppView = Backbone.View.extend({});  
var appView= new AppView({}); 

 (1)先看Backbone.View的源碼:

var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
var View = Backbone.View =  function(options) {
     // _.uniqueId也是underscore.js里的一個函數,作用是其內部通過閉包保存了一個整型變量,每次調用此函數整型變量都會自增1,然后用傳進來的前綴(此處前綴是view)連接整型變量為一個字符串,返回該字符串
     this.cid = _.uniqueId('view');
     // 調用Backbone.View.prototype._configure,作用是合並options與AppView.prototype.options,並且將指定屬性名且值不等同於false的屬性設置到this(_configure在后面有具體說明)
     this._configure(options || {});
     // 根據this.el是否被賦值,做一些處理
     this._ensureElement();
     // 將Backbone.View.prototype.initialize作為this的方法調用,Backbone.View.prototype.initialize默認是一個內容為空的函數。一般情況下,我們會在options中定義一個initialize函數(在AppView=Backbone.View.extend({initialize:...})這里定義,其實就是AppView.prototype.initialize)來覆蓋Backbone.View.prototype.initialize
     this.initialize.apply( this, arguments);
     this.delegateEvents();
  };  

 (2)_configure函數被我簡化如下表示:

_.extend(View.prototype,  { 
    _configure:  function(options) {
           // 此處this指向我們自定義View的一個實例,也就是appView

           // 判斷this有沒有options屬性,如果有則將this.options與options合並然后賦值給options,通過解析extend方法得出的原型對象鏈我們分析得知由於此時this還沒有被實例化完成且Backbon.View.prototype沒有this屬性,那么this.options只能來自AppView.prototype(ctor的一個實例),也就是說此處的this.options是在在Backbone.View.extend({options:...})中指定的,而options是AppView這個構造函數的第一個參數
           if ( this.options) options = _.extend({},  this.options, options);
           // 遍歷viewOptions,當options[viewOptions[i]]有值時,將其賦值給this
           for ( var i = 0, l = viewOptions.length; i < l; i++) {
         var attr = viewOptions[i];
         if (options[attr])  this[attr] = options[attr];
          }
           this.options = options;
    }
});

 (3)delegateEvents代碼解析

   // 一個正則,匹配未被空格、換行等分隔符分隔的字符串,或者以空格、換行等分隔符分隔為2段的字符串 
   var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  delegateEvents:  function(events) {
       // 判斷events屬性(可以使一個對象,也可以是一個返回對象的函數)是否存在,如果不存在則返回
       if (!(events || (events = getValue( this, 'events'))))  return;
       // 移除this.$el上綁定的事件
       this.undelegateEvents();
       // 遍歷所有指定的事件
       for ( var key  in events) {
    // events[key]要么是一個函數,要么是View的一個方法名
         var method = events[key];
         if (!_.isFunction(method)) method =  this[events[key]];
     // 如果未找到事件對於的函數,則拋出移除
         if (!method)  throw  new Error('Method "' + events[key] + '" does not exist');
     // 對事件名進行切割,例如"click .toggle"會被切割為match=['click .toggle','click','.toggle']
         var match = key.match(delegateEventSplitter);
     // 設置事件名,以及選擇器
         var eventName = match[1], selector = match[2];
         // _.bind是underscore.js的方法,此處調用此方法,得到一個函數method,函數內部的this引用的是我們自定義View的一個實例,也就是appView
        method = _.bind(method,  this);
         // 為事件添加命名空間,以便可以通過命名空間一次性移除所有不同類型的事件
        eventName += '.delegateEvents' +  this.cid;
//下面使用jQuery進行事件綁定
         // 如果選擇器為空字符串,則給$el綁定事件
         if (selector === '') {
           this.$el.bind(eventName, method);
        }  else {
          //根據選擇器,為this.$el的子級綁定事件
           this.$el.delegate(selector, eventName, method);
        }
      }
  }
   // 附上undelegateEvents的源碼
  undelegateEvents:  function() {
       //移除當前通過this.$el.bind或者this.$el.delegate綁定的且命名空間為 '.delegateEvents' + this.cid 的所有事件(通過命名空間來移除)
       this.$el.unbind('.delegateEvents' +  this.cid);
  }

   // 附上getValue的源碼
   var getValue =  function(object, prop) {
     if (!(object && object[prop]))  return  null;
     return _.isFunction(object[prop]) ? object[prop]() : object[prop];
  };

(4) _.bind源碼解析,將其代碼簡化如下:

    FuncProto = Function.prototype;
    nativeBind         = FuncProto.bind;
     var ArrayProto = Array.prototype;
     var slice            = ArrayProto.slice;
     var ctor =  function(){};
    _.bind =  function bind(func, context) {
         var bound, args;
         // 判斷當前瀏覽器是否實現了EcmaScript 5.1的Function.prototype.bind方法,如果是,則直接調用此方法並返回一個函數
         // 如果在此處返回,那么返回的函數參數列表是:(arguments[2],arguments[3],...),且函數里面的this引用的是context
         if (func.bind === nativeBind && nativeBind)  return nativeBind.apply(func, slice.call(arguments, 1));
         // 如果func不是一個函數,則返回    
         if (!_.isFunction(func))  throw  new TypeError;
         // 獲取第三個及其后面的所有參數(也就是獲取context以后的所有參數)
        args = slice.call(arguments, 2);
         return bound =  function() {
           // 如果不是用bound當作構造函數,則在此處返回一個函數
           if (!( this  instanceof bound)) 
          {
             // 如果在此處返回,那么返回的函數參數列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函數里面的this引用的是context
             // 注意,args是調用外層_.bind函數時的arguments的一段(利用閉包實現),arguments則是調用此處返回的函數時引用的參數對象
             return func.apply(context, args.concat(slice.call(arguments)));
          }
           // ctor是一個內容為空的構造函數,此處將其原型對象設置為func.prototype
          ctor.prototype = func.prototype;
           // 實例化一個ctor對象
           var self =  new ctor;
           // 上面兩句代碼執行以后,構造了一個作用域鏈: self 所屬類--> ctor  原型對象--> func.prototype

           // 調用func函數,得到一個返回對象。返回的函數參數列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函數里面的this引用的是self
           // 注意,args是調用外層_.bind函數時的arguments的一段(利用閉包實現),arguments則是調用此處返回的函數時引用的參數對象
           var result = func.apply(self, args.concat(slice.call(arguments)));

           // 下面代碼是返回一個對象,注意:在構造函數里返回對象,則構造函數中this引用的對象會被拋棄
        
           // 如果result是引用類型(如Object,Array等),則返回result
           if (Object(result) === result) 
          {        
             return result;
          }
           // 否則返回self
           return self;
        };
   };

 

(5) 關於EcmaScript 5.1的Function.prototype.bind方法

 Function.prototype.bind(thisArg [, arg1 [, arg2, …]])

Function.prototype.bind返回一個新的函數對象,該函數對象的this綁定到了thisArg參數上。從本質上講,這允許你在其他對象鏈中執行一個函數。
例子如下:
    function locate(){
      console.log( this.location);
    }
     function Maru(location){
       this.location = location;
    }
     var kitty =  new Maru("cardboard box");
     var locateMaru = locate.bind(kitty);
    locateMaru();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM