jQuery中的編程範式

  瀏覽器前端編程的面貌自2005年以來已經發生了深入的變化,這並不簡單的意味着出現了大量功能豐富的基礎庫,使得咱們能夠更加方便的編寫業務代碼,更重要的是咱們看待前端技術的觀念發生了重大轉變,明確意識到了如何之前端特有的方式釋放程序員的生產力。本文將結合jQuery源碼的實現原理,對javascript中涌現出的編程範式和經常使用技巧做一簡單介紹。
  
1. AJAX: 狀態駐留,異步更新
  首先來看一點歷史。
A. 1995年Netscape公司的Brendan Eich開發了javacript語言,這是一種動態(dynamic)、弱類型(weakly typed)、基於原型(prototype-based)的腳本語言。
B. 1999年微軟IE5發佈,其中包含了XMLHTTP ActiveX控件。
C. 2001年微軟IE6發佈,部分支持DOM level 1和CSS 2標準。
D. 2002年Douglas Crockford發明JSON格式。
至此,能夠說Web2.0所依賴的技術元素已經基本成形,可是並無馬上在整個業界產生重大的影響。儘管一些「頁面異步局部刷新」的技巧在程序員中間祕密的流傳,甚至催生了bindows這樣龐大臃腫的類庫,但總的來講,前端被看做是貧瘠而又骯髒的沼澤地,只有後臺技術纔是王道。到底還缺乏些什麼呢?
  當咱們站在今天的角度去回顧2005年以前的js代碼,包括那些當時的牛人所寫的代碼,能夠明顯的感覺到它們在程序控制力上的孱弱。並非說2005年以前的js技術自己存在問題,只是它們在概念層面上是人心渙散,缺少統一的觀念,或者說缺乏本身獨特的風格, 本身的靈魂。當時大多數的人,大多數的技術都試圖在模擬傳統的面嚮對象語言,利用傳統的面向對象技術,去實現傳統的GUI模型的仿製品。
  2005年是變革的一年,也是創造概念的一年。伴隨着Google一系列讓人耳目一新的交互式應用的發佈,Jesse James Garrett的一篇文章《Ajax: A New Approach to Web Applications》被廣爲傳播。Ajax這一前端特有的概念迅速將衆多分散的實踐統一在同一口號之下,引起了Web編程範式的轉換。所謂名不正則言不順,這下無名羣衆可找到組織了。在未有Ajax以前,人們早已認識到了B/S架構的本質特徵在於瀏覽器和服務器的狀態空間是分離的,可是通常的解決方案都是隱藏這一區分,將前臺狀態同步到後臺,由後臺統一進行邏輯處理,例如ASP.NET。由於缺少成熟的設計模式支持前臺狀態駐留,在換頁的時候,已經裝載的js對象將被迫被丟棄,這樣誰還能期望它去完成什麼複雜的工做嗎?
  Ajax明確提出界面是局部刷新的,前臺駐留了狀態,這就促成了一種須要:須要js對象在前臺存在更長的時間。這也就意味着須要將這些對象和功能有效的管理起來,意味着更復雜的代碼組織技術,意味着對模塊化,對公共代碼基的渴求。
  jQuery現有的代碼中真正與Ajax相關(使用XMLHTTP控件異步訪問後臺返回數據)的部分其實不多,可是若是沒有Ajax, jQuery做爲公共代碼基也就缺少存在的理由。

2. 模塊化:管理名字空間
  當大量的代碼產生出來之後,咱們所須要的最基礎的概念就是模塊化,也就是對工做進行分解和複用。工做得以分解的關鍵在於各人獨立工做的成果能夠集成在一塊兒。這意味着各個模塊必須基於一致的底層概念,能夠實現交互,也就是說應該基於一套公共代碼基,屏蔽底層瀏覽器的不一致性,並實現統一的抽象層,例如統一的事件管理機制等。比統一代碼基更重要的是,各個模塊之間必須沒有名字衝突。不然,即便兩個模塊之間沒有任何交互,也沒法共同工做。
  jQuery目前鼓吹的主要賣點之一就是對名字空間的良好控制。這甚至比提供更多更完善的功能點都重要的多。良好的模塊化容許咱們複用任何來源的代碼,全部人的工做得以積累疊加。而功能實現僅僅是一時的工做量的問題。jQuery使用module pattern的一個變種來減小對全局名字空間的影響,僅僅在window對象上增長了一個jQuery對象(也就是$函數)。
   所謂的module pattern代碼以下,它的關鍵是利用匿名函數限制臨時變量的做用域。
  var feature =(function() {

// 私有變量和函數
var privateThing = 'secret',
    publicThing = 'not secret',

    changePrivateThing = function() {
        privateThing = 'super secret';
    },

    sayPrivateThing = function() {
        console.log(privateThing);
        changePrivateThing();
    };

// 返回對外公開的API
return {
    publicThing : publicThing,
    sayPrivateThing :  sayPrivateThing
}
})();
  js自己缺少包結構,不過通過多年的嘗試以後業內已經逐漸統一了對包加載的認識,造成了RequireJs庫這樣獲得必定共識的解決方案。jQuery能夠與RequireJS庫良好的集成在一塊兒, 實現更完善的模塊依賴管理。http://requirejs.org/docs/jquery.html
  
  require(["jquery", "jquery.my"], function() {
    //當jquery.js和jquery.my.js都成功裝載以後執行
    $(function(){
      $('#my').myFunc();
    });
  });
  
  經過如下函數調用來定義模塊my/shirt, 它依賴於my/cart和my/inventory模塊,
  require.def("my/shirt",
    ["my/cart", "my/inventory"],
    function(cart, inventory) {
        // 這裏使用module pattern來返回my/shirt模塊對外暴露的API
        return {
            color: "blue",
            size: "large"
            addToCart: function() {
                // decrement是my/inventory對外暴露的API
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
  );

3. 神奇的$:對象提高
  當你第一眼看到$函數的時候,你想到了什麼?傳統的編程理論老是告訴咱們函數命名應該準確,應該清晰無誤的表達做者的意圖,甚至聲稱長名字要優於短名字,由於減小了出現歧義的可能性。可是,$是什麼?亂碼?它所傳遞的信息實在是太隱晦,太曖昧了。$是由prototype.js庫發明的,它真的是一個神奇的函數,由於它能夠將一個原始的DOM節點提高(enhance)爲一個具備複雜行爲的對象。在prototype.js最初的實現中,$函數的定義爲
  var $ = function (id) {
    return "string" == typeof id ? document.getElementById(id) : id;
  };
  這基本對應於以下公式
      e = $(id)
  這毫不僅僅是提供了一個聰明的函數名稱縮寫,更重要的是在概念層面上創建了文本id與DOM element之間的一一對應。在未有$以前,id與對應的element之間的距離十分遙遠,通常要將element緩存到變量中,例如
  var ea = docuement.getElementById('a');
  var eb = docuement.getElementById('b');
  ea.style....
可是使用$以後,卻隨處可見以下的寫法
  $('header_'+id).style...
  $('body_'+id)....
id與element之間的距離彷佛被消除了,能夠很是緊密的交織在一塊兒。
  prototype.js後來擴展了$的含義,
  function $() {
    var elements = new Array();
    
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (typeof element == 'string')
          element = document.getElementById(element);
    
        if (arguments.length == 1)
          return element;
    
        elements.push(element);
    }
    
    return elements;
  }
  這對應於公式
    [e,e] = $(id,id)
  很遺憾,這一步prototype.js走偏了,這一作法不多有實用的價值。
  真正將$發揚光大的是jQuery, 它的$對應於公式
    [o] = $(selector)
  這裏有三個加強
  A. selector再也不是單一的節點定位符,而是複雜的集合選擇符
  B. 返回的元素不是原始的DOM節點,而是通過jQuery進一步加強的具備豐富行爲的對象,能夠啓動複雜的函數調用鏈。
  C. $返回的包裝對象被造型爲數組形式,將集合操做天然的整合到調用鏈中。

  固然,以上僅僅是對神奇的$的一個過度簡化的描述,它的實際功能要複雜得多. 特別是有一個很是經常使用的直接構造功能. 
   $("<table><tbody><tr><td>...</td></tr></tbody></table>")....
  jQuery將根據傳入的html文本直接構造出一系列的DOM節點,並將其包裝爲jQuery對象. 這在某種程度上能夠看做是對selector的擴展: html內容描述自己就是一種惟一指定. 
  $(function{})這一功能就實在是讓人有些無語了, 它表示當document.ready的時候調用此回調函數。真的,$是一個神奇的函數, 有任何問題,請$一下。
  總結起來, $是從普通的DOM和文本描述世界到具備豐富對象行爲的jQuery世界的躍遷通道。跨過了這道門,就來到了理想國。
   
4. 無定形的參數:專一表達而不是約束
  弱類型語言既然頭上頂着個"弱"字, 總不免讓人有些先天不足的感受. 在程序中缺少類型約束, 是否真的是一種重大的缺憾? 在傳統的強類型語言中, 函數參數的類型,個數等都是由編譯器負責檢查的約束條件, 但這些約束仍然是遠遠不夠的. 通常應用程序中爲了增強約束, 總會增長大量防護性代碼, 例如在C++中咱們經常使用ASSERT, 而在java中也常常須要判斷參數值的範圍
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);            
  很顯然, 這些代碼將致使程序中存在大量無功能的執行路徑, 即咱們作了大量判斷, 代碼執行到某個點, 系統拋出異常, 大喊此路不通. 若是咱們換一個思路, 既然已經作了某種判斷,可否利用這些判斷的結果來作些什麼呢? javascript是一種弱類型的語言,它是沒法自動約束參數類型的, 那若是順勢而行,進一步弱化參數的形態, 將"弱"推動到一種極致, 在弱無可弱的時候, weak會不會成爲標誌性的特色? 
  看一下jQuery中的事件綁定函數bind, 
   A. 一次綁定一個事件 $("#my").bind("mouseover", function(){});
   B. 一次綁定多個事件 $("#my").bind("mouseover mouseout",function(){})
   C. 換一個形式, 一樣綁定多個事件 
      $("#my").bind({mouseover:function(){}, mouseout:function(){});
   D. 想給事件監聽器傳點參數
      $('#my').bind('click', {foo: "xxxx"}, function(event) { event.data.foo..})
   E. 想給事件監聽器分個組
      $("#my").bind("click.myGroup″, function(){});
   F. 這個函數爲何尚未瘋掉???
   
   就算是類型不肯定, 在固定位置上的參數的意義總要是肯定的吧? 退一萬步來講, 就算是參數位置不重要了,函數自己的意義應該是肯定的吧? 但這是什麼? 
      取值 value = o.val(), 設置值 o.val(3)
      
   一個函數怎麼能夠這樣過度, 怎麼能根據傳入參數的類型和個數不一樣而行爲不一樣呢? 看不順眼是否是? 可這就是俺們的價值觀. 既然不能防止, 那就故意容許. 雖然形式多變, 卻無一句廢話. 缺乏約束, 不妨礙表達(我不是出來嚇人的). 
   
5. 鏈式操做: 線性化的逐步細化
  jQuery早期最主要的賣點就是所謂的鏈式操做(chain). 
  
  $('#content') // 找到content元素
    .find('h3') // 選擇全部後代h3節點
    .eq(2)      // 過濾集合, 保留第三個元素
        .html('改變第三個h3的文本')
    .end()      // 返回上一級的h3集合
    .eq(0)
        .html('改變第一個h3的文本');

在通常的命令式語言中, 咱們總須要在重重嵌套循環中過濾數據, 實際操做數據的代碼與定位數據的代碼糾纏在一塊兒. 而jQuery採用先構造集合而後再應用函數於集合的方式實現兩種邏輯的解耦, 實現嵌套結構的線性化. 實際上, 咱們並不須要藉助過程化的思想就能夠很直觀的理解一個集合, 例如 $('div.my input:checked')能夠看做是一種直接的描述,而不是對過程行爲的跟蹤. 
   循環意味着咱們的思惟處於一種反覆迴繞的狀態, 而線性化以後則沿着一個方向直線前進, 極大減輕了思惟負擔, 提升了代碼的可組合性. 爲了減小調用鏈的中斷, jQuery發明了一個絕妙的主意: jQuery包裝對象自己相似數組(集合). 集合能夠映射到新的集合, 集合能夠限制到本身的子集合,調用的發起者是集合,返回結果也是集合,集合能夠發生結構上的某種變化但它仍是集合, 集合是某種概念上的不動點,這是從函數式語言中吸收的設計思想。集合操做是太常見的操做, 在java中咱們很容易發現大量所謂的封裝函數其實就是在封裝一些集合遍歷操做, 而在jQuery中集合操做由於太直白而不須要封裝. 
   鏈式調用意味着咱們始終擁有一個「當前」對象,全部的操做都是針對這一當前對象進行。這對應於以下公式
     x += dx
調用鏈的每一步都是對當前對象的增量描述,是針對最終目標的逐步細化過程。Witrix平臺中對這一思想也有着普遍的應用。特別是爲了實現平臺機制與業務代碼的融合,平臺會提供對象(容器)的缺省內容,而業務代碼能夠在此基礎上進行逐步細化的修正,包括取消缺省的設置等。
  話說回來, 雖然表面上jQuery的鏈式調用很簡單, 內部實現的時候卻必須本身多寫一層循環, 由於編譯器並不知道"自動應用於集合中每一個元素"這回事. 
  $.fn['someFunc'] = function(){
    return this.each(function(){
      jQuery.someFunc(this,...);
    }
  }
  
6. data: 統一數據管理
  做爲一個js庫,它必須解決的一個大問題就是js對象與DOM節點之間的狀態關聯與協同管理問題。有些js庫選擇以js對象爲主,在js對象的成員變量中保存DOM節點指針,訪問時老是以js對象爲入口點,經過js函數間接操做DOM對象。在這種封裝下,DOM節點其實只是做爲界面展示的一種底層「彙編」而已。jQuery的選擇與Witrix平臺相似,都是以HTML自身結構爲基礎,經過js加強(enhance)DOM節點的功能,將它提高爲一個具備複雜行爲的擴展對象。這裏的思想是非侵入式設計(non-intrusive)和優雅退化機制(graceful degradation)。語義結構在基礎的HTML層面是完整的,js的做用是加強了交互行爲,控制了展示形式。
  若是每次咱們都經過$('#my')的方式來訪問相應的包裝對象,那麼一些須要長期保持的狀態變量保存在什麼地方呢?jQuery提供了一個統一的全局數據管理機制。
  獲取數據 $('#my').data('myAttr')   設置數據 $('#my').data('myAttr',3);
這一機制天然融合了對HTML5的data屬性的處理
   <input id="my" data-my-attr="4" ... />
 經過 $('#my').data('myAttr')將能夠讀取到HTML中設置的數據。
 
 第一次訪問data時,jQuery將爲DOM節點分配一個惟一的uuid, 而後設置在DOM節點的一個特定的expando屬性上, jQuery保證這個uuid在本頁面中不重複。
   elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
 以上代碼能夠同時處理DOM節點和純js對象的狀況。若是是js對象,則data直接放置在js對象自身中,而若是是DOM節點,則經過cache統一管理。
 由於全部的數據都是經過data機制統一管理的,特別是包括全部事件監聽函數(data.events),所以jQuery能夠安全的實現資源管理。在clone節點的時候,能夠自動clone其相關的事件監聽函數。而當DOM節點的內容被替換或者DOM節點被銷燬的時候,jQuery也能夠自動解除事件監聽函數, 並安全的釋放相關的js數據。
 
7. event:統一事件模型
  "事件沿着對象樹傳播"這一圖景是面向對象界面編程模型的精髓所在。對象的複合構成對界面結構的一個穩定的描述,事件不斷在對象樹的某個節點發生,並經過冒泡機制向上傳播。對象樹很天然的成爲一個控制結構,咱們能夠在父節點上監聽全部子節點上的事件,而不用明確與每個子節點創建關聯。
  jQuery除了爲不一樣瀏覽器的事件模型創建了統一抽象以外,主要作了以下加強:
  A. 增長了自定製事件(custom)機制. 事件的傳播機制與事件內容自己原則上是無關的, 所以自定製事件徹底能夠和瀏覽器內置事件經過同一條處理路徑, 採用一樣的監聽方式. 使用自定製事件能夠加強代碼的內聚性, 減小代碼耦合. 例如若是沒有自定製事件, 關聯代碼每每須要直接操做相關的對象
  $('.switch, .clapper').click(function() {
    var $light = $(this).parent().find('.lightbulb');
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
  });
而若是使用自定製事件,則表達的語義更加內斂明確,
  $('.switch, .clapper').click(function() {
    $(this).parent().find('.lightbulb').trigger('changeState');
  });
  B. 增長了對動態建立節點的事件監聽. bind函數只能將監聽函數註冊到已經存在的DOM節點上. 例如
    $('li.trigger').bind('click',function(){}}
  若是調用bind以後,新建了另外一個li節點,則該節點的click事件不會被監聽. 
  jQuery的delegate機制能夠將監聽函數註冊到父節點上, 子節點上觸發的事件會根據selector被自動派發到相應的handlerFn上. 這樣一來如今註冊就能夠監聽將來建立的節點. 
    $('#myList').delegate('li.trigger', 'click', handlerFn);
  最近jQuery1.7中統一了bind, live和delegate機制, 天下一統, 只有on/off.
    $('li.trigger’).on('click', handlerFn);  // 至關於bind
    $('#myList’).on('click', 'li.trigger', handlerFn);  // 至關於delegate
    
8. 動畫隊列:全局時鐘協調
  拋開jQuery的實現不談, 先考慮一下若是咱們要實現界面上的動畫效果, 到底須要作些什麼? 好比咱們但願將一個div的寬度在1秒鐘以內從100px增長到200px. 很容易想見, 在一段時間內咱們須要不時的去調整一下div的寬度, [同時]咱們還須要執行其餘代碼. 與通常的函數調用不一樣的是, 發出動畫指令以後, 咱們不能期待馬上獲得想要的結果, 並且咱們不能原地等待結果的到來. 動畫的複雜性就在於:一次性表達以後要在一段時間內執行,並且有多條邏輯上的執行路徑要同時展開, 如何協調? 
  偉大的艾薩克.牛頓爵士在《天然哲學的數學原理》中寫道:"絕對的、真正的和數學的時間自身在流逝着". 全部的事件能夠在時間軸上對齊, 這就是它們內在的協調性. 所以爲了從步驟A1執行到A5, 同時將步驟B1執行到B5, 咱們只須要在t1時刻執行[A1, B1], 在t2時刻執行[A2,B2], 依此類推. 
    t1 | t2 | t3 | t4 | t5 ...
    A1 | A2 | A3 | A4 | A5 ...
    B1 | B2 | B3 | B4 | B5 ...
  具體的一種實現形式能夠是 
  A. 對每一個動畫, 將其分裝爲一個Animation對象, 內部分紅多個步驟.
      animation = new Animation(div,"width",100,200,1000,
                  負責步驟切分的插值函數,動畫執行完畢時的回調函數);
  B. 在全局管理器中註冊動畫對象
      timerFuncs.add(animation);
  C. 在全局時鐘的每個觸發時刻, 將每一個註冊的執行序列推動一步, 若是已經結束, 則從全局管理器中刪除.
     for each animation in timerFuncs
        if(!animation.doOneStep())
           timerFuncs.remove(animation)

  解決了原理問題,再來看看錶達問題, 怎樣設計接口函數纔可以以最緊湊形式表達咱們的意圖? 咱們常常須要面臨的實際問題:
  A. 有多個元素要執行相似的動畫
  B. 每一個元素有多個屬性要同時變化
  C. 執行完一個動畫以後開始另外一個動畫
jQuery對這些問題的解答能夠說是榨盡了js語法表達力的最後一點剩餘價值. 
   $('input')
     .animate({left:'+=200px',top:'300'},2000)
     .animate({left:'-=200px',top:20},1000)
     .queue(function(){
       // 這裏dequeue將首先執行隊列中的後一個函數,所以alert("y")
       $(this).dequeue();
       alert('x');
      })
     .queue(function(){
        alert("y");
        // 若是不主動dequeue, 隊列執行就中斷了,不會自動繼續下去.
        $(this).dequeue();
      });

  A. 利用jQuery內置的selector機制天然表達對一個集合的處理.
  B. 使用Map表達多個屬性變化
  C. 利用微格式表達領域特定的差量概念. '+=200px'表示在現有值的基礎上增長200px
  D. 利用函數調用的順序自動定義animation執行的順序: 在後面追加到執行隊列中的動畫天然要等前面的動畫徹底執行完畢以後再啓動.
   
  jQuery動畫隊列的實現細節大概以下所示,
   A. animate函數實際是調用queue(function(){執行結束時須要調用dequeue,不然不會驅動下一個方法})
      queue函數執行時, 若是是fx隊列, 而且當前沒有正在運行動畫(若是連續調用兩次animate,第二次的執行函數將在隊列中等待),則會自動觸發dequeue操做, 驅動隊列運行.
      若是是fx隊列, dequeue的時候會自動在隊列頂端加入"inprogress"字符串,表示將要執行的是動畫.
   B. 針對每個屬性,建立一個jQuery.fx對象。而後調用fx.custom函數(至關於start)來啓動動畫。
   C. custom函數中將fx.step函數註冊到全局的timerFuncs中,而後試圖啓動一個全局的timer.
       timerId = setInterval( fx.tick, fx.interval );
   D. 靜態的tick函數中將依次調用各個fx的step函數。step函數中經過easing計算屬性的當前值,而後調用fx的update來更新屬性。
   E. fx的step函數中判斷若是全部屬性變化都已完成,則調用dequeue來驅動下一個方法。

  頗有意思的是, jQuery的實現代碼中明顯有不少是接力觸發代碼: 若是須要執行下一個動畫就取出執行, 若是須要啓動timer就啓動timer等. 這是由於js程序是單線程的,真正的執行路徑只有一條,爲了保證執行線索不中斷, 函數們不得不互相幫助一下. 能夠想見, 若是程序內部具備多個執行引擎, 甚至無限多的執行引擎, 那麼程序的面貌就會發生本質性的改變. 而在這種情形下, 遞歸相對於循環而言會成爲更天然的描述. 
  
9. promise模式:因果關係的識別
  現實中,總有那麼多時間線在獨立的演化着, 人與物在時空中交錯,卻沒有發生因果. 軟件中, 函數們在源代碼中排着隊, 不免會產生一些疑問, 憑什麼排在前面的要先執行? 難道沒有它就沒有我? 讓全宇宙喊着1,2,3齊步前進, 從上帝的角度看,大概是管理難度過大了, 因而便有了相對論. 若是相互之間沒有交換信息, 沒有產生相互依賴, 那麼在某個座標系中順序發生的事件, 在另一個座標系中看來, 就多是顛倒順序的. 程序員依葫蘆畫瓢, 便發明了promise模式. 
  promise與future模式基本上是一回事,咱們先來看一下java中熟悉的future模式.
  futureResult = doSomething();
  ...
  realResult = futureResult.get();
  發出函數調用僅僅意味着一件事情發生過, 並沒必要然意味着調用者須要瞭解事情最終的結果. 函數馬上返回的只是一個將在將來兌現的承諾(Future類型), 實際上也就是某種句柄. 句柄被傳來傳去, 中間轉手的代碼對實際結果是什麼,是否已經返回不聞不問. 直到一段代碼須要依賴調用返回的結果, 所以它打開future, 查看了一下. 若是實際結果已經返回, 則future.get()馬上返回實際結果, 不然將會阻塞當前的執行路徑, 直到結果返回爲止. 此後再調用future.get()老是馬上返回, 由於因果關係已經被創建, [結果返回]這一事件必然在此以前發生, 不會再發生變化. 
  future模式通常是外部對象主動查看future的返回值, 而promise模式則是由外部對象在promise上註冊回調函數. 
  function getData(){
   return $.get('/foo/').done(function(){
      console.log('Fires after the AJAX request succeeds');
   }).fail(function(){
      console.log('Fires after the AJAX request fails');
   });
  }
 
  function showDiv(){
    var dfd = $.Deferred();
    $('#foo').fadeIn( 1000, dfd.resolve );
    return dfd.promise();
  }
 
  $.when( getData(), showDiv() )
    .then(function( ajaxResult, ignoreResultFromShowDiv ){
        console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
        // 'ajaxResult' is the server’s response
    });
  jQuery引入Deferred結構, 根據promise模式對ajax, queue, document.ready等進行了重構, 統一了異步執行機制. then(onDone, onFail)將向promise中追加回調函數, 若是調用成功完成(resolve), 則回調函數onDone將被執行, 而若是調用失敗(reject), 則onFail將被執行. when能夠等待在多個promise對象上. promise巧妙的地方是異步執行已經開始以後甚至已經結束以後,仍然能夠註冊回調函數
  someObj.done(callback).sendRequest() vs. someObj.sendRequest().done(callback)
 callback函數在發出異步調用以前註冊或者在發出異步調用以後註冊是徹底等價的, 這揭示出程序表達永遠不是徹底精確的, 總存在着內在的變化維度. 若是能有效利用這一內在的可變性, 則能夠極大提高併發程序的性能. 
   promise模式的具體實現很簡單. jQuery._Deferred定義了一個函數隊列,它的做用有如下幾點:
   A. 保存回調函數。
   B. 在resolve或者reject的時刻把保存着的函數所有執行掉。
   C. 已經執行以後, 再增長的函數會被馬上執行。
  
   一些專門面向分佈式計算或者並行計算的語言會在語言級別內置promise模式, 好比E語言. 
     def carPromise := carMaker <- produce("Mercedes");
     def temperaturePromise := carPromise <- getEngineTemperature()
     ...
     when (temperaturePromise) -> done(temperature) {
       println(`The temperature of the car engine is: $temperature`)
     } catch e {
       println(`Could not get engine temperature, error: $e`)
     }
  在E語言中, <-是eventually運算符, 表示最終會執行, 但不必定是如今. 而普通的car.moveTo(2,3)表示馬上執行獲得結果. 編譯器負責識別全部的promise依賴, 並自動實現調度. 
  
10. extend: 繼承不是必須的
  js是基於原型的語言, 並無內置的繼承機制, 這一直讓不少深受傳統面向對象教育的同窗們耿耿於懷. 但繼承必定是必須的嗎? 它到底可以給咱們帶來什麼? 最純樸的回答是: 代碼重用. 那麼, 咱們首先來分析一下繼承做爲代碼重用手段的潛力. 
  曾經有個概念叫作"多重繼承", 它是繼承概念的超級賽亞人版, 很遺憾後來被診斷爲存在着先天缺陷, 以至於出現了一種對於繼承概念的解讀: 繼承就是"is a"關係, 一個派生對象"is a"不少基類, 必然會出現精神分裂, 因此多重繼承是很差的. 
   class A{ public: void f(){ f in A } }
   class B{ public: void f(){ f in B } }
   class D: public A, B{}
 若是D類從A,B兩個基類繼承, 而A和B類中都實現了同一個函數f, 那麼D類中的f究竟是A中的f仍是B中的f, 抑或是A中的f+B中的f呢? 這一困境的出現實際上源於D的基類A和B是並列關係, 它們知足交換律和結合律, 畢竟,在概念層面上咱們可能難以承認兩個任意概念之間會出現從屬關係. 但若是咱們放鬆一些概念層面的要求, 更多的從操做層面考慮一下代碼重用問題, 能夠簡單的認爲B在A的基礎上進行操做, 那麼就能夠獲得一個線性化的結果. 也就是說, 放棄A和B之間的交換律只保留結合律, extends A, B 與 extends B,A 會是兩個不一樣的結果, 再也不存在詮釋上的二義性. scala語言中的所謂trait(特性)機制實際上採用的就是這一策略. 
  面向對象技術發明好久以後, 出現了所謂的面向方面編程(AOP), 它與OOP不一樣, 是代碼結構空間中的定位與修改技術. AOP的眼中只有類與方法, 不知道什麼叫作意義. AOP也提供了一種相似多重繼承的代碼重用手段, 那就是mixin. 對象被看做是能夠被打開,而後任意修改的Map, 一組成員變量與方法就被直接注射到對象體內, 直接改變了它的行爲. 
  prototype.js庫引入了extend函數,
  Object.extend = function(destination, source) {
    for (var property in source) {
      destination[property] = source[property];
    }
    return destination;
  }
  就是Map之間的一個覆蓋運算, 但很管用, 在jQuery庫中也獲得了延用. 這個操做相似於mixin, 在jQuery中是代碼重用的主要技術手段---沒有繼承也沒什麼大不了的. 

11. 名稱映射: 一切都是數據 
  代碼好很差, 循環判斷必須少. 循環和判斷語句是程序的基本組成部分, 可是優良的代碼庫中卻每每找不到它們的蹤跡, 由於這些語句的交織會模糊系統的邏輯主線, 使咱們的思想迷失在疲於奔命的代碼追蹤中. jQuery自己經過each, extend等函數已經極大減小了對循環語句的需求, 對於判斷語句, 則主要是經過映射表來處理. 例如, jQuery的val()函數須要針對不一樣標籤進行不一樣的處理, 所以定義一個以tagName爲key的函數映射表
   valHooks: { option: {get:function(){}}}
這樣在程序中就不須要處處寫
   if(elm.tagName == 'OPTION'){
     return ...;
   }else if(elm.tagName == 'TEXTAREA'){
     return ...;
   }
能夠統一處理
   (valHooks[elm.tagName.toLowerCase()] || defaultHandler).get(elm);
   
  映射表將函數做爲普通數據來管理, 在動態語言中有着普遍的應用. 特別是, 對象自己就是函數和變量的容器, 能夠被看做是映射表. jQuery中大量使用的一個技巧就是利用名稱映射來動態生成代碼, 造成一種相似模板的機制. 例如爲了實現myWidth和myHeight兩個很是相似的函數, 咱們不須要
  jQuery.fn.myWidth = function(){
      return parseInt(this.style.width,10) + 10;
    }
    
    jQuery.fn.myHeight = function(){
      return parseInt(this.style.height,10) + 10;
    }
而能夠選擇動態生成
    jQuery.each(['Width','Height'],function(name){
      jQuery.fn['my'+name] = function(){
        return parseInt(this.style[name.toLowerCase()],10) + 10;
      }
    });
  
12. 插件機制:其實我很簡單    
  jQuery所謂的插件其實就是$.fn上增長的函數, 那這個fn是什麼東西?
  (function(window,undefined){
    // 內部又有一個包裝
    var jQuery = (function() {
      var jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context, rootjQuery );
        }
       ....
      // fn實際就是prototype的簡寫
      jQuery.fn = jQuery.prototype = {
          constructor: jQuery,
          init: function( selector, context, rootjQuery ) {...  }
      }
    
      // 調用jQuery()就是至關於new init(), 而init的prototype就是jQuery的prototype
      jQuery.fn.init.prototype = jQuery.fn;
    
      // 這裏返回的jQuery對象只具有最基本的功能, 下面就是一系列的extend
      return jQuery;
    })();  
    ...
     // 將jQuery暴露爲全局對象
    window.jQuery = window.$ = jQuery;
  })(window);
  顯然, $.fn其實就是jQuery.prototype的簡寫. 
  
  無狀態的插件僅僅就是一個函數, 很是簡單. 
  // 定義插件
  (function($){
      $.fn.hoverClass = function(c) {
          return this.hover(
              function() { $(this).toggleClass(c); }
          );
      };
  })(jQuery);
  
  // 使用插件
  $('li').hoverClass('hover');
  
 對於比較複雜的插件開發, jQuery UI提供了一個widget工廠機制, 
 $.widget("ui.dialog", {
   options: {
        autoOpen: true,...
     },
     _create: function(){ ... },
     _init: function() {
        if ( this.options.autoOpen ) {
            this.open();
        }
     },
     _setOption: function(key, value){ ... }
     destroy: function(){ ... }
 });
 
 調用 $('#dlg').dialog(options)時, 實際執行的代碼基本以下所示:
  this.each(function() {
        var instance = $.data( this, "dialog" );
        if ( instance ) {
            instance.option( options || {} )._init();
        } else {
            $.data( this, "dialog", new $.ui.dialog( options, this ) );
        }
    }
 能夠看出, 第一次調用$('#dlg').dialog()函數時會建立窗口對象實例,並保存在data中, 此時會調用_create()和_init()函數, 而若是不是第一次調用, 則是在已經存在的對象實例上調用_init()方法. 屢次調用$('#dlg').dialog()並不會建立多個實例. 

13. browser sniffer vs. feature detection
  瀏覽器嗅探(browser sniffer)曾經是很流行的技術, 好比早期的jQuery中
  jQuery.browser = {
        version:(userAgent.match(/.+(?:rv|it|ra|ie)[/: ]([d.]+)/) || [0,'0'])[1],
        safari:/webkit/.test(userAgent),
        opera:/opera/.test(userAgent),
        msie:/msie/.test(userAgent) && !/opera/.test(userAgent),
        mozilla:/mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
  };
  在具體代碼中能夠針對不一樣的瀏覽器做出不一樣的處理
  if($.browser.msie) {
      // do something
  } else if($.browser.opera) {
      // ...
  }  

  可是隨着瀏覽器市場的競爭升級, 競爭對手之間的互相模仿和假裝致使userAgent一片混亂, 加上Chrome的誕生, Safari的崛起, IE也開始加速向標準靠攏, sniffer已經起不到積極的做用. 特性檢測(feature detection)做爲更細粒度, 更具體的檢測手段, 逐漸成爲處理瀏覽器兼容性的主流方式. 
  jQuery.support = {
        // IE strips leading whitespace when .innerHTML is used
        leadingWhitespace: ( div.firstChild.nodeType === 3 ),
        ...
    }
    只基於實際看見的,而不是曾經知道的, 這樣更容易作到兼容將來. 

14. Prototype vs. jQuery
  prototype.js是一個立意高遠的庫, 它的目標是提供一種新的使用體驗,參照Ruby從語言級別對javascript進行改造,並最終真的極大改變了js的面貌。$, extends, each, bind...這些耳熟能詳的概念都是prototype.js引入到js領域的. 它肆無忌憚的在window全局名字空間中增長各類概念, 大有誰先佔坑誰有理, 捨我其誰的氣勢. 而jQuery則扣扣索索, 抱着比較實用化的理念, 目標僅僅是write less, do more而已.  
  不過等待激進的理想主義者的命運每每都是壯志未酬身先死. 當prototype.js標誌性的bind函數等被吸取到ECMAScript標準中時, 便註定了它的沒落. 處處修改原生對象的prototype, 這是prototype.js的獨門祕技, 也是它的死穴. 特別是當它試圖模仿jQuery, 經過Element.extend(element)返回加強對象的時候, 算是完全被jQuery給帶到溝裏去了. prototype.js與jQuery不一樣, 它老是直接修改原生對象的prototype, 而瀏覽器倒是充滿bug, 謊話, 歷史包袱並夾雜着商業陰謀的領域, 在原生對象層面解決問題註定是一場悲劇. 性能問題, 名字衝突, 兼容性問題等等都是一個幫助庫的能力所沒法解決的. Prototype.js的2.0版本聽說要作大的變革, 不知是要與歷史決裂, 放棄兼容性, 仍是繼續掙扎, 在夾縫中求生.javascript

相關文章
相關標籤/搜索