亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

jQuery中的編程范式詳解

2019-11-20 13:44:16
字體:
來源:轉載
供稿:網友

本文詳細分析了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版本據說要做大的變革, 不知是要與歷史決裂, 放棄兼容性, 還是繼續掙扎, 在夾縫中求生.

希望本文所述對大家的jQuery程序設計有所幫助。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久777国产线看观看精品| 久久久亚洲精品视频| 国产精品va在线播放| 91精品国产91久久久久| 日韩视频在线免费观看| 97在线看免费观看视频在线观看| 在线播放国产一区中文字幕剧情欧美| 日韩在线小视频| 欧美激情第6页| 日韩精品免费在线视频观看| 91成人性视频| 成人激情视频在线观看| 久久亚洲国产精品成人av秋霞| 国产精品电影一区| 欧美电影在线观看网站| 日本一区二三区好的精华液| 亚洲精品永久免费| 日韩欧美精品免费在线| 一本色道久久88综合亚洲精品ⅰ| 国产精品久久久久久av| 久热在线中文字幕色999舞| 97超视频免费观看| 亚洲影视九九影院在线观看| 精品国产乱码久久久久久虫虫漫画| 亚洲娇小xxxx欧美娇小| 日韩av电影在线网| 国产97在线播放| 91精品视频一区| 欧美日韩色婷婷| 国产不卡一区二区在线播放| 久久这里只有精品视频首页| 精品久久久久久久久久久久久| 成年无码av片在线| 亚洲第一综合天堂另类专| 91九色视频导航| 欧美麻豆久久久久久中文| 久久精品99久久香蕉国产色戒| 国产欧美婷婷中文| 亚洲精品美女在线观看播放| 国产精品激情av在线播放| 亚洲精品视频久久| 亚洲国产99精品国自产| 亚洲图片制服诱惑| 亚洲九九九在线观看| 日韩欧美黄色动漫| 成人444kkkk在线观看| 88xx成人精品| 国产香蕉97碰碰久久人人| 国产精品免费看久久久香蕉| 日本久久久久久久| 日韩在线一区二区三区免费视频| 色综合久久精品亚洲国产| 久久高清视频免费| 日本精品一区二区三区在线播放视频| 久久久精品国产亚洲| 综合网日日天干夜夜久久| 欧美洲成人男女午夜视频| 亚洲精品国精品久久99热一| 一本色道久久综合狠狠躁篇的优点| 欧美老少配视频| 久久av红桃一区二区小说| 国产精品久久久久久久久久ktv| 亚洲成人激情在线观看| 亚洲第一免费网站| 欧美成人在线免费视频| 精品久久在线播放| 91成人免费观看网站| 亚洲第一精品久久忘忧草社区| 在线a欧美视频| 成人在线观看视频网站| 青青草成人在线| 国产精品福利久久久| 8050国产精品久久久久久| 亚洲电影免费观看高清完整版在线| 97视频免费观看| 日韩成人性视频| 992tv在线成人免费观看| 国产大片精品免费永久看nba| 欧美激情综合亚洲一二区| 久热精品视频在线观看| 欧美性20hd另类| 国产精品美女av| 69av在线播放| 欧美国产日韩一区二区三区| 欧美韩国理论所午夜片917电影| 一区二区欧美激情| 亚洲福利视频网站| 久久久中精品2020中文| 日韩av综合中文字幕| 97超级碰碰人国产在线观看| 久久噜噜噜精品国产亚洲综合| 亚洲第一在线视频| 亚洲成色www8888| 国产视频亚洲精品| 久色乳综合思思在线视频| 国产精品高潮呻吟久久av野狼| 日韩在线欧美在线国产在线| 欧美极品欧美精品欧美视频| 曰本色欧美视频在线| 夜夜躁日日躁狠狠久久88av| 高清日韩电视剧大全免费播放在线观看| 亚洲欧洲国产精品| 国产精品视频在线观看| 欧美成人精品在线| 国内久久久精品| 国产精品高潮视频| 日本国产高清不卡| 久久人人爽人人爽人人片av高请| 伊人久久大香线蕉av一区二区| 精品亚洲永久免费精品| 国产精品永久免费| 蜜臀久久99精品久久久无需会员| 精品国产福利视频| 国产精品欧美一区二区| 69久久夜色精品国产69| 亚洲成人xxx| 久久全球大尺度高清视频| 亚洲国产精品久久91精品| 成人激情视频免费在线| 国产99视频在线观看| 狠狠躁夜夜躁人人躁婷婷91| 国产精品嫩草影院久久久| 日韩亚洲国产中文字幕| 一区二区在线视频| 亚洲成人久久久久| 日本精品一区二区三区在线| 欧美劲爆第一页| 色综久久综合桃花网| 国产噜噜噜噜久久久久久久久| 91国产美女在线观看| 中文字幕9999| 91在线播放国产| 日韩视频在线一区| 久久久精品一区| 成人h猎奇视频网站| 亚洲嫩模很污视频| 亚洲国产精久久久久久久| 亚洲福利在线观看| 麻豆成人在线看| 欧美日韩中文在线| 欧美黑人又粗大| 久久久精品视频成人| 操人视频在线观看欧美| 欧美成人免费大片| 成人黄色av播放免费| 亚洲欧美综合精品久久成人| 久久久久久久一| 国产美女精品免费电影| 久久天天躁狠狠躁夜夜爽蜜月| 91爱视频在线| 亚洲欧美日本另类| 亚洲第一av网| 日韩视频―中文字幕| 亚洲xxx自由成熟| 成人有码在线播放| 日韩中文字幕视频在线| 国产欧美日韩中文| 一区二区亚洲欧洲国产日韩| 国产精品老女人视频| 国产精品美女主播| 亚洲aⅴ男人的天堂在线观看| 日韩激情视频在线播放| 欧美丰满少妇xxxx| 亚洲精品电影网|